Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
7de86ebd40 | |||
29fb39e07e | |||
92ee533955 | |||
e73263a657 | |||
7afe153706 | |||
9ff99120a9 | |||
e0e8ee0d58 | |||
8dae85d136 | |||
64dddb4495 | |||
0d24787c17 | |||
9daaebcd74 | |||
86f9ca2c5b | |||
0a74b67a07 | |||
51d708165e | |||
6ab14a31a0 | |||
931839da8d | |||
bb7fa56db4 | |||
f9d31d4743 | |||
f41e8c734d | |||
07637a734f | |||
3782f2fc97 | |||
a84032c5d3 | |||
b65a8bb883 | |||
4110c2118f | |||
1beaa7889e | |||
ef43127a56 | |||
8ed8bdcbd9 | |||
9f3e4369fc | |||
666207c507 | |||
04073743f5 | |||
8c5e8a5229 | |||
ddbb5fdf8a | |||
a667f9984f | |||
5c3e7bcdee | |||
507e076eca | |||
b319718ac6 | |||
328daeb684 | |||
55b3ee8ee5 | |||
76f2d243b4 | |||
8f347d2dc8 | |||
7e6f01351d | |||
9257dfa692 | |||
c95c766d51 | |||
3743614b84 | |||
430eddf70f | |||
2fdc05b7f6 | |||
8fdff0e02a | |||
4a3d3f02b0 | |||
eccc58782a |
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
Onomatopoeia-front
|
2
.idea/deploymentTargetSelector.xml
generated
2
.idea/deploymentTargetSelector.xml
generated
@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<SelectionState runConfigName="presentation">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
|
5
.idea/gradle.xml
generated
5
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
@ -9,7 +10,9 @@
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/data" />
|
||||
<option value="$PROJECT_DIR$/domain" />
|
||||
<option value="$PROJECT_DIR$/presentation" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
|
57
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
57
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,57 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -5,4 +5,5 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.hilt) apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.7.10" apply false
|
||||
}
|
@ -3,6 +3,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("kotlin-kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -47,4 +48,5 @@ dependencies {
|
||||
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.converter.gson)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
|
||||
}
|
6
data/src/main/java/com/nto/data/models/LoginResult.kt
Normal file
6
data/src/main/java/com/nto/data/models/LoginResult.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package com.nto.data.models
|
||||
|
||||
data class LoginResult(
|
||||
val successful: Boolean,
|
||||
val message: String?
|
||||
)
|
6
data/src/main/java/com/nto/data/models/QRDTO.kt
Normal file
6
data/src/main/java/com/nto/data/models/QRDTO.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package com.nto.data.models
|
||||
|
||||
data class QRDTO(
|
||||
val id: Long = Long.MAX_VALUE,
|
||||
val name: String = ""
|
||||
)
|
7
data/src/main/java/com/nto/data/models/RequestResult.kt
Normal file
7
data/src/main/java/com/nto/data/models/RequestResult.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package com.nto.data.models
|
||||
|
||||
enum class RequestResult {
|
||||
OK,
|
||||
CANCELED,
|
||||
ERROR
|
||||
}
|
18
data/src/main/java/com/nto/data/models/UserDTO.kt
Normal file
18
data/src/main/java/com/nto/data/models/UserDTO.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package com.nto.data.models
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
enum class Position {
|
||||
DEVELOPER, DESIGNER, TESTER, ANALYST, ADMINISTRATOR
|
||||
}
|
||||
|
||||
data class UserDTO(
|
||||
val firstName: String = "",
|
||||
val lastName: String = "",
|
||||
val patronymic: String = "",
|
||||
val position: Position = Position.DEVELOPER,
|
||||
val lastVisit: LocalDateTime = LocalDateTime.now(),
|
||||
val isError: Boolean = false,
|
||||
val isUnauthorized: Boolean = false,
|
||||
val localizedName: String = ""
|
||||
)
|
16
data/src/main/java/com/nto/data/models/cards/VisitCardDTO.kt
Normal file
16
data/src/main/java/com/nto/data/models/cards/VisitCardDTO.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.nto.data.models.cards
|
||||
|
||||
import com.nto.data.models.QRDTO
|
||||
import java.time.LocalDateTime
|
||||
|
||||
enum class VisitType {
|
||||
PHONE_ENTRY, CARD_ENTRY
|
||||
}
|
||||
|
||||
data class VisitCardDTO(
|
||||
val name: String = "",
|
||||
val id: Long = Long.MAX_VALUE,
|
||||
val date: LocalDateTime = LocalDateTime.now(),
|
||||
val visitType: VisitType = VisitType.CARD_ENTRY,
|
||||
val qrCode: QRDTO = QRDTO()
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
package com.nto.data.models.cards
|
||||
|
||||
data class VisitCardWrapper(
|
||||
val data: List<VisitCardDTO>?,
|
||||
val isError: Boolean = false,
|
||||
)
|
18
data/src/main/java/com/nto/data/repository/DataRepository.kt
Normal file
18
data/src/main/java/com/nto/data/repository/DataRepository.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package com.nto.data.repository
|
||||
|
||||
import com.nto.data.models.LoginResult
|
||||
import com.nto.data.models.RequestResult
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardWrapper
|
||||
|
||||
interface DataRepository {
|
||||
suspend fun auth(login: String, password: String): LoginResult
|
||||
suspend fun saveToken(token: String, login: String)
|
||||
suspend fun getToken(): String
|
||||
suspend fun getLogin(): String
|
||||
suspend fun getInfo(login: String): UserDTO
|
||||
suspend fun getVisits(id: String): VisitCardWrapper
|
||||
suspend fun open(): RequestResult
|
||||
suspend fun logout()
|
||||
suspend fun invertLock(login: String, status: Boolean): RequestResult
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.nto.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import com.nto.data.models.LoginResult
|
||||
import com.nto.data.models.RequestResult
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardWrapper
|
||||
import com.nto.data.utils.Provider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.Credentials
|
||||
import javax.inject.Inject
|
||||
|
||||
class DataRepositoryImpl @Inject constructor(@ApplicationContext private val context: Context) :
|
||||
DataRepository {
|
||||
override suspend fun auth(login: String, password: String): LoginResult {
|
||||
val token = Credentials.basic(login, password)
|
||||
val result = Provider.provideRetrofit().auth(
|
||||
token
|
||||
).execute()
|
||||
if (result.isSuccessful) saveToken(token, login)
|
||||
return LoginResult(result.isSuccessful, result.message())
|
||||
}
|
||||
|
||||
override suspend fun saveToken(token: String, login: String) {
|
||||
context.getSharedPreferences("auth", MODE_PRIVATE).edit().putString("token", token)
|
||||
.putString("login", login).apply()
|
||||
}
|
||||
|
||||
override suspend fun getToken(): String {
|
||||
return context.getSharedPreferences("auth", MODE_PRIVATE).getString("token", "")!!
|
||||
}
|
||||
|
||||
override suspend fun getLogin(): String {
|
||||
return context.getSharedPreferences("auth", MODE_PRIVATE).getString("login", "")!!
|
||||
}
|
||||
|
||||
override suspend fun getInfo(login: String): UserDTO {
|
||||
val result = Provider.provideRetrofit().getInfo(token = getToken(), login = login).execute()
|
||||
return if (result.isSuccessful) result.body()!!
|
||||
else UserDTO(isError = true, isUnauthorized = result.code() == 403)
|
||||
}
|
||||
|
||||
override suspend fun getVisits(id: String): VisitCardWrapper {
|
||||
val result = Provider.provideRetrofit().getVisits(getToken(), id.ifBlank { getLogin() }).execute()
|
||||
return if (result.isSuccessful) {
|
||||
VisitCardWrapper(result.body()!!)
|
||||
} else {
|
||||
VisitCardWrapper(null, true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun open(): RequestResult {
|
||||
val result = Provider.provideRetrofit().open(getToken()).execute()
|
||||
return when (result.code()) {
|
||||
200 -> RequestResult.OK
|
||||
400, 403 -> RequestResult.CANCELED
|
||||
else -> RequestResult.ERROR
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
context.getSharedPreferences("auth", MODE_PRIVATE).edit().remove("token").remove("login")
|
||||
.apply()
|
||||
}
|
||||
|
||||
override suspend fun invertLock(login: String, status: Boolean): RequestResult {
|
||||
return when (Provider.provideRetrofit().invertLock(getToken(), login, status).execute()
|
||||
.code()) {
|
||||
200 -> RequestResult.OK
|
||||
400, 403 -> RequestResult.CANCELED
|
||||
else -> RequestResult.ERROR
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,22 @@
|
||||
package com.nto.data.utils
|
||||
|
||||
sealed class Destinations{
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed class Destinations {
|
||||
@Serializable
|
||||
object Login
|
||||
|
||||
@Serializable
|
||||
object Profile
|
||||
object Scan
|
||||
|
||||
@Serializable
|
||||
data class Scan(
|
||||
val value: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
object Admin
|
||||
|
||||
@Serializable
|
||||
object Options
|
||||
}
|
22
data/src/main/java/com/nto/data/utils/Provider.kt
Normal file
22
data/src/main/java/com/nto/data/utils/Provider.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.nto.data.utils
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object Provider {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(): RetrofitApi {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://v-chat.ru/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build().create(RetrofitApi::class.java)
|
||||
}
|
||||
}
|
31
data/src/main/java/com/nto/data/utils/RetrofitApi.kt
Normal file
31
data/src/main/java/com/nto/data/utils/RetrofitApi.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package com.nto.data.utils
|
||||
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardDTO
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface RetrofitApi {
|
||||
@GET("employee/auth")
|
||||
fun auth(@Header("Authorization") token: String): Call<ResponseBody>
|
||||
|
||||
@GET("employee/{login}/info")
|
||||
fun getInfo(@Header("Authorization") token: String, @Path("login") login: String): Call<UserDTO>
|
||||
|
||||
@GET("visits/{login}/visits")
|
||||
fun getVisits(@Header("Authorization") token: String, @Path("login") login:String): Call<List<VisitCardDTO>>
|
||||
|
||||
@GET("visit/open")
|
||||
fun open(@Header("Authorization") token: String): Call<ResponseBody>
|
||||
|
||||
@PUT("employee/invertLock")
|
||||
fun invertLock(
|
||||
@Header("Authorization") token: String, @Query("login") login: String, @Body status: Boolean
|
||||
): Call<ResponseBody>
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.nto.domain.repository
|
||||
|
||||
import com.nto.data.models.LoginResult
|
||||
import com.nto.data.models.RequestResult
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardWrapper
|
||||
|
||||
interface DomainRepository {
|
||||
suspend fun auth(email: String, password: String): LoginResult
|
||||
suspend fun saveToken(token: String, login: String)
|
||||
suspend fun getToken(): String?
|
||||
suspend fun getLogin(): String
|
||||
suspend fun getInfo(login: String): UserDTO
|
||||
suspend fun getVisits(id: String): VisitCardWrapper
|
||||
suspend fun open(): RequestResult
|
||||
suspend fun logout()
|
||||
suspend fun invertLock(id: String, status: Boolean): RequestResult
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.nto.domain.repository
|
||||
|
||||
import com.nto.data.models.LoginResult
|
||||
import com.nto.data.models.RequestResult
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardWrapper
|
||||
import com.nto.data.repository.DataRepositoryImpl
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class DomainRepositoryImpl @Inject constructor(private val dataRepositoryImpl: DataRepositoryImpl) :
|
||||
DomainRepository {
|
||||
override suspend fun auth(email: String, password: String): LoginResult {
|
||||
return try {
|
||||
dataRepositoryImpl.auth(
|
||||
login = email, password = password
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
LoginResult(false, "IO exception was thrown: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveToken(token: String, login: String) {
|
||||
dataRepositoryImpl.saveToken(token, login)
|
||||
}
|
||||
|
||||
override suspend fun getToken(): String? {
|
||||
return try {
|
||||
dataRepositoryImpl.getToken()
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getLogin(): String {
|
||||
return dataRepositoryImpl.getLogin()
|
||||
}
|
||||
|
||||
override suspend fun getInfo(login: String): UserDTO {
|
||||
return try {
|
||||
return dataRepositoryImpl.getInfo(login.ifBlank { getLogin() })
|
||||
} catch (e: IOException) {
|
||||
UserDTO(isError = true, isUnauthorized = true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getVisits(id: String): VisitCardWrapper {
|
||||
return try {
|
||||
dataRepositoryImpl.getVisits(id)
|
||||
} catch (e: IOException) {
|
||||
VisitCardWrapper(null, isError = true)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun open(): RequestResult {
|
||||
return dataRepositoryImpl.open()
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
dataRepositoryImpl.logout()
|
||||
}
|
||||
|
||||
override suspend fun invertLock(id: String, status: Boolean): RequestResult {
|
||||
return dataRepositoryImpl.invertLock(id, status)
|
||||
}
|
||||
|
||||
}
|
10
domain/src/main/java/com/nto/domain/usecase/AdminUseCase.kt
Normal file
10
domain/src/main/java/com/nto/domain/usecase/AdminUseCase.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.nto.domain.usecase
|
||||
|
||||
import com.nto.domain.repository.DomainRepositoryImpl
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdminUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) {
|
||||
suspend fun invertLock(id: String, status: Boolean){
|
||||
domainRepositoryImpl.invertLock(id, status)
|
||||
}
|
||||
}
|
15
domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt
Normal file
15
domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.nto.domain.usecase
|
||||
|
||||
import com.nto.data.models.LoginResult
|
||||
import com.nto.domain.repository.DomainRepositoryImpl
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) {
|
||||
fun checkCredentials(email: String, password: String): Boolean {
|
||||
return password.isNotBlank() && email.isNotBlank()
|
||||
}
|
||||
|
||||
suspend fun auth(email: String, password: String): LoginResult {
|
||||
return domainRepositoryImpl.auth(email, password)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.nto.domain.usecase
|
||||
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardWrapper
|
||||
import com.nto.domain.repository.DomainRepositoryImpl
|
||||
import javax.inject.Inject
|
||||
|
||||
class ProfileUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) {
|
||||
suspend fun getInfo(): UserDTO{
|
||||
return domainRepositoryImpl.getInfo("")
|
||||
}
|
||||
suspend fun logout(){
|
||||
return domainRepositoryImpl.logout()
|
||||
}
|
||||
suspend fun getVisits(): VisitCardWrapper {
|
||||
return domainRepositoryImpl.getVisits("")
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package com.nto.domain.usecase
|
||||
|
||||
class ScanUseCase {
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.nto.domain.usecase
|
||||
|
||||
import com.nto.domain.repository.DomainRepositoryImpl
|
||||
import javax.inject.Inject
|
||||
|
||||
class SplashScreenUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) {
|
||||
suspend fun getToken(): String? {
|
||||
return domainRepositoryImpl.getToken()
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
activityCompose = "1.10.0"
|
||||
composeBom = "2024.04.01"
|
||||
composeBom = "2024.09.03"
|
||||
|
||||
kapt = "2.1.10"
|
||||
|
||||
|
@ -4,6 +4,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
id("kotlin-kapt")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -69,8 +70,9 @@ dependencies {
|
||||
kapt(libs.hilt.compiler)
|
||||
implementation(libs.hilt)
|
||||
implementation (libs.hilt.navigation)
|
||||
implementation(libs.navigation)
|
||||
|
||||
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.8.3")
|
||||
lintChecks(libs.lint.checks)
|
||||
lintChecks(libs.lint.checks.compose)
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:name=".di.App"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:name=".di.App"
|
||||
android:theme="@style/Theme.Onomatopoeiafront">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@ -19,6 +20,11 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".CustomCaptureActivity"
|
||||
android:stateNotNeeded="true"
|
||||
android:theme="@style/zxing_CaptureTheme"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,5 @@
|
||||
package com.nto.presentation
|
||||
|
||||
import com.journeyapps.barcodescanner.CaptureActivity
|
||||
|
||||
class CustomCaptureActivity : CaptureActivity()
|
@ -3,34 +3,44 @@ package com.nto.presentation
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.nto.presentation.composable.Navigation
|
||||
import com.nto.presentation.screens.splashScreen.SplashScreenViewModel
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.TextColor
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
val viewmodel by viewModels<SplashScreenViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
NTOTheme {
|
||||
//XML SUCKS! We use Jetpack Compose btw :>
|
||||
this.window.statusBarColor = TextColor.toArgb()
|
||||
viewmodel.checkLogin().apply {
|
||||
setContent {
|
||||
NTOTheme {
|
||||
//XML SUCKS! We use Jetpack Compose btw :>
|
||||
val state = viewmodel.state.collectAsState().value
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
val navController = rememberNavController()
|
||||
Navigation(
|
||||
navController = navController,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
)
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
val navController = rememberNavController()
|
||||
Navigation(
|
||||
navController = navController,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
skipAuth = state.skipAuth
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,154 @@
|
||||
package com.nto.presentation.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.nto.presentation.composable.DecoratedButtonType.Default
|
||||
import com.nto.presentation.composable.DecoratedButtonType.Disabled
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.raleway
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
enum class DecoratedButtonType {
|
||||
Default, Disabled
|
||||
}
|
||||
|
||||
object DecoratedButtonValues {
|
||||
private val DefaultColors: ButtonColors
|
||||
@Composable get() = ButtonColors(
|
||||
contentColor = NTOTheme.colors.primaryBackground,
|
||||
containerColor = NTOTheme.colors.button,
|
||||
disabledContentColor = Color.Unspecified,
|
||||
disabledContainerColor = Color.Unspecified
|
||||
)
|
||||
|
||||
private val DisabledColors: ButtonColors
|
||||
@Composable get() = ButtonColors(
|
||||
disabledContentColor = NTOTheme.colors.primaryBackground,
|
||||
disabledContainerColor = NTOTheme.colors.buttonDisabled,
|
||||
contentColor = NTOTheme.colors.primaryBackground,
|
||||
containerColor = NTOTheme.colors.button
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun getDefaultColor(type: DecoratedButtonType): ButtonColors {
|
||||
return when (type) {
|
||||
Default -> DefaultColors
|
||||
Disabled -> DisabledColors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* High level element that utilizes [DecoratedButtonType] to obtain [DecoratedButtonValues] and use it's colors accordingly.
|
||||
*
|
||||
* Consists of a button with a text inside it. By fact that's just a wrapper to simplify reusing process.
|
||||
*
|
||||
* @param text text to display inside a button.
|
||||
* @param isDisabled mutable variable that represents button state. Should be in viewmodel or screen state. After being converted to the instance of [DecoratedButtonType] that is used to obtain [ButtonColors] inside [DecoratedButtonValues].
|
||||
* @param modifier modifier that should contain [Modifier.height] and [Modifier.width] or other size definition to work correctly.
|
||||
* @param shape element will be clipped to that shape.
|
||||
* @param onClick function to be invoked on button click.
|
||||
*
|
||||
* @sample [DecoratedButtonSample]
|
||||
*
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun DecoratedButton(
|
||||
text: String,
|
||||
isDisabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = RoundedCornerShape(10.dp),
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
NTOTheme {
|
||||
Button(
|
||||
modifier = modifier, shape = shape, colors = DecoratedButtonValues.getDefaultColor(
|
||||
when (isDisabled) {
|
||||
true -> Disabled
|
||||
false -> Default
|
||||
}
|
||||
), onClick = onClick,
|
||||
enabled = !isDisabled
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 14.sp,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.secondaryText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
internal class DecoratedButtonSample {
|
||||
class SampleViewModel {
|
||||
private val _state = MutableStateFlow(SampleState())
|
||||
|
||||
val state: StateFlow<SampleState>
|
||||
get() = _state.asStateFlow()
|
||||
|
||||
fun setDisabledState(data: Boolean) {
|
||||
_state.value = _state.value.copy(data = data)
|
||||
}
|
||||
}
|
||||
|
||||
data class SampleState(
|
||||
var data: Boolean = false
|
||||
)
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputFieldPreview() {
|
||||
NTOTheme {
|
||||
val sampleViewModel = SampleViewModel()
|
||||
val state = sampleViewModel.state.collectAsState()
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
//Filled button preset
|
||||
DecoratedButton(
|
||||
text = "Get Started",
|
||||
isDisabled = state.value.data,
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.width(300.dp),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
) {
|
||||
//...
|
||||
}
|
||||
//Disabled button preset
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
DecoratedButton(
|
||||
text = "Get Started",
|
||||
isDisabled = !state.value.data,
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.width(300.dp),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
) {
|
||||
//...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
@ -39,6 +40,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.theme.BoxGray
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.raleway
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@ -56,7 +58,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
class InputFieldOptions(
|
||||
val containerColor: Color = BoxGray,
|
||||
val textFieldColors: TextFieldColors? = null,
|
||||
val paddingValues: PaddingValues = PaddingValues(start = 20.dp),
|
||||
val paddingValues: PaddingValues = PaddingValues(start = 10.dp),
|
||||
val isConfidential: Boolean = false
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -123,13 +125,18 @@ fun InputField(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
onValueChange = onValueChange,
|
||||
textStyle = NTOTheme.typography.displaySmall,
|
||||
textStyle = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontFamily = raleway
|
||||
),
|
||||
placeholder = {
|
||||
Text(
|
||||
placeholder,
|
||||
style = NTOTheme.typography.displaySmall,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.disabledText,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
},
|
||||
@ -148,7 +155,7 @@ fun InputField(
|
||||
}
|
||||
} else null,
|
||||
visualTransformation = if (!options.isConfidential) VisualTransformation.None
|
||||
else if (state!!.value) PasswordVisualTransformation()
|
||||
else if (state!!.value) PasswordVisualTransformation('*')
|
||||
else VisualTransformation.None)
|
||||
}
|
||||
|
||||
@ -184,7 +191,7 @@ internal class InputFieldSample {
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.fillMaxWidth(),
|
||||
placeholder = stringResource(R.string.placholder_email),
|
||||
placeholder = stringResource(R.string.placholder_login),
|
||||
onValueChange = sampleViewModel::setText,
|
||||
options = InputFieldOptions(isConfidential = true)
|
||||
)
|
||||
|
@ -6,22 +6,31 @@ import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.presentation.screens.admin.AdminScreen
|
||||
import com.nto.presentation.screens.loginScreen.LoginScreen
|
||||
import com.nto.presentation.screens.profileScreen.ProfileScreen
|
||||
import com.nto.presentation.screens.scanResult.ScanResultScreen
|
||||
|
||||
@Composable
|
||||
fun Navigation(navController: NavHostController, modifier: Modifier = Modifier) {
|
||||
fun Navigation(navController: NavHostController, skipAuth: Boolean, modifier: Modifier = Modifier) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
modifier = modifier,
|
||||
startDestination = Destinations.Login.toString()
|
||||
startDestination = if (skipAuth) Destinations.Profile else Destinations.Login
|
||||
) {
|
||||
composable(Destinations.Login.toString()) {
|
||||
composable<Destinations.Login> {
|
||||
LoginScreen(navController)
|
||||
}
|
||||
composable(Destinations.Profile.toString()){
|
||||
//TODO
|
||||
composable<Destinations.Profile> {
|
||||
ProfileScreen(navController)
|
||||
}
|
||||
composable(Destinations.Scan.toString()){
|
||||
composable<Destinations.Scan> {
|
||||
ScanResultScreen(navController)
|
||||
}
|
||||
composable<Destinations.Admin> {
|
||||
AdminScreen(navController)
|
||||
}
|
||||
composable<Destinations.Options> {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.nto.presentation.composable
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
|
||||
@Composable
|
||||
fun UserCard(user: UserDTO, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(NTOTheme.colors.inputFieldBackground)
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(text = "@${user.firstName}")
|
||||
if (false) {
|
||||
Text(
|
||||
text = "разблокировать",
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = NTOTheme.colors.button,
|
||||
)
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
Text(text = "${user.lastName} ${user.firstName} ${user.patronymic}")
|
||||
Text(text = user.localizedName)
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.nto.presentation.composable.cards
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.nto.data.models.cards.VisitCardDTO
|
||||
import com.nto.data.models.cards.VisitType
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.raleway
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Composable
|
||||
fun VisitCard(data: VisitCardDTO, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.width(365.dp)
|
||||
.height(70.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(NTOTheme.colors.inputFieldBackground),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 15.dp, end = 15.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.Start) {
|
||||
Text(
|
||||
text = data.name,
|
||||
fontSize = 14.sp,
|
||||
fontFamily = raleway,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = NTOTheme.colors.primaryText
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = data.id.toString(),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText
|
||||
)
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
Text(
|
||||
text = data.date.format(dateFormat),
|
||||
fontSize = 14.sp,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.disabledText
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = stringResource(if (data.visitType == VisitType.PHONE_ENTRY) R.string.label_qr_login else R.string.label_card_login),
|
||||
fontFamily = raleway,
|
||||
fontSize = 12.sp,
|
||||
color = NTOTheme.colors.disabledText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun VisitCardPreview() {
|
||||
NTOTheme {
|
||||
VisitCard(VisitCardDTO("Кабинет 207"))
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.nto.presentation.screens.admin
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.composable.InputField
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.playfair
|
||||
import com.nto.presentation.theme.raleway
|
||||
|
||||
@Composable
|
||||
fun AdminScreen(
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: AdminViewModel = hiltViewModel()
|
||||
) {
|
||||
Scaffold { innerPaddings ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(innerPaddings)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp)
|
||||
) {
|
||||
Spacer(Modifier.height(36.dp))
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
text = stringResource(R.string.admin_header),
|
||||
fontFamily = playfair,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.border(BorderStroke(2.dp, NTOTheme.colors.buttonDisabled), CircleShape),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_arrow_back),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
stringResource(R.string.text_login),
|
||||
fontFamily = raleway,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
InputField(
|
||||
viewModel.login,
|
||||
placeholder = stringResource(R.string.placholder_login),
|
||||
onValueChange = viewModel::setLogin
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.nto.presentation.screens.admin
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AdminViewModel @Inject constructor(): ViewModel() {
|
||||
private var _login by mutableStateOf("")
|
||||
|
||||
val login get() = _login
|
||||
|
||||
fun setLogin(value: String) {
|
||||
_login = value
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -21,6 +22,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -29,9 +31,12 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.composable.DecoratedButton
|
||||
import com.nto.presentation.composable.InputField
|
||||
import com.nto.presentation.composable.InputFieldOptions
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.playfair
|
||||
import com.nto.presentation.theme.raleway
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
@ -61,16 +66,20 @@ fun LoginScreen(
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.greeting_login),
|
||||
style = NTOTheme.typography.titleLarge,
|
||||
fontFamily = playfair,
|
||||
fontSize = 64.sp,
|
||||
color = NTOTheme.colors.secondaryText,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.greeting_login_description),
|
||||
style = NTOTheme.typography.displaySmall,
|
||||
fontFamily = raleway,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 16.sp,
|
||||
color = NTOTheme.colors.secondaryText,
|
||||
textAlign = TextAlign.Center
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.width(300.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Column(
|
||||
@ -81,19 +90,21 @@ fun LoginScreen(
|
||||
RoundedCornerShape(topStart = 21.dp, topEnd = 21.dp)
|
||||
)
|
||||
.background(NTOTheme.colors.primaryBackground)
|
||||
.padding(40.dp)
|
||||
.padding(start = 24.dp, end = 24.dp, top = 40.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
stringResource(R.string.text_email),
|
||||
style = NTOTheme.typography.displaySmall,
|
||||
stringResource(R.string.text_login),
|
||||
fontFamily = raleway,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(5.dp))
|
||||
InputField(
|
||||
state.email,
|
||||
placeholder = stringResource(R.string.placholder_email),
|
||||
placeholder = stringResource(R.string.placholder_login),
|
||||
onValueChange = viewModel::setEmail
|
||||
)
|
||||
}
|
||||
@ -101,7 +112,9 @@ fun LoginScreen(
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
stringResource(R.string.text_password),
|
||||
style = NTOTheme.typography.displaySmall,
|
||||
fontFamily = raleway,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
@ -114,7 +127,15 @@ fun LoginScreen(
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(50.dp))
|
||||
//TODO: LoginButton
|
||||
DecoratedButton(
|
||||
stringResource(R.string.action_login),
|
||||
state.disabled,
|
||||
modifier = Modifier
|
||||
.width(364.dp)
|
||||
.height(60.dp)
|
||||
) {
|
||||
viewModel.login(navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,26 @@
|
||||
package com.nto.presentation.screens.loginScreen
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.domain.usecase.LoginUseCase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.invoke
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel @Inject constructor(): ViewModel(){
|
||||
class LoginViewModel @Inject constructor(
|
||||
private val useCase: LoginUseCase, @ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
private val _state = MutableStateFlow(LoginScreenState())
|
||||
|
||||
val state: StateFlow<LoginScreenState>
|
||||
@ -29,12 +37,29 @@ class LoginViewModel @Inject constructor(): ViewModel(){
|
||||
}
|
||||
|
||||
private fun checkInput() {
|
||||
//TODO: domain level
|
||||
val state = _state.value
|
||||
val result = useCase.checkCredentials(state.email, state.password)
|
||||
_state.tryEmit(_state.value.copy(disabled = !result))
|
||||
}
|
||||
|
||||
fun login(navController: NavHostController) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
//TODO: domain level
|
||||
val state = _state.value
|
||||
val result = useCase.auth(state.email, state.password)
|
||||
if (result.message != null) {
|
||||
Dispatchers.Main {
|
||||
Toast.makeText(context, result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
if (result.successful) {
|
||||
Dispatchers.Main {
|
||||
navController.navigate(Destinations.Profile) {
|
||||
popUpTo<Destinations.Login> {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
package com.nto.presentation.screens.profileScreen
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.presentation.CustomCaptureActivity
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.composable.DecoratedButton
|
||||
import com.nto.presentation.composable.cards.VisitCard
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.TextGray
|
||||
import com.nto.presentation.theme.playfair
|
||||
import com.nto.presentation.theme.raleway
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: ProfileViewModel = hiltViewModel<ProfileViewModel>(),
|
||||
) {
|
||||
val state = viewModel.state.collectAsState().value
|
||||
val scannerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ScanContract(),
|
||||
onResult = { result -> navController.navigate(Destinations.Scan(result.contents)) }
|
||||
)
|
||||
val scanOptions = ScanOptions()
|
||||
scanOptions.setPrompt("")
|
||||
scanOptions.setBeepEnabled(false)
|
||||
scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
scanOptions.setOrientationLocked(false)
|
||||
scanOptions.setCaptureActivity(CustomCaptureActivity::class.java)
|
||||
|
||||
LaunchedEffect(state.isUnauthorized) {
|
||||
if (state.isUnauthorized) {
|
||||
navController.navigate(Destinations.Login)
|
||||
}
|
||||
}
|
||||
PullToRefreshBox(
|
||||
isRefreshing = viewModel.isLoading,
|
||||
onRefresh = {
|
||||
viewModel.updateInfo()
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(NTOTheme.colors.primaryBackground)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 20.dp, end = 20.dp)
|
||||
.verticalScroll(
|
||||
rememberScrollState()
|
||||
)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.title_profile),
|
||||
fontFamily = playfair,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
navController.navigate(Destinations.Admin)
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.border(
|
||||
BorderStroke(2.dp, NTOTheme.colors.buttonAdmin),
|
||||
CircleShape
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.icon_admin),
|
||||
contentDescription = null,
|
||||
tint = NTOTheme.colors.buttonAdmin
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
IconButton(
|
||||
onClick = {},
|
||||
modifier = Modifier
|
||||
.size(44.dp)
|
||||
.border(
|
||||
BorderStroke(2.dp, NTOTheme.colors.buttonDisabled),
|
||||
CircleShape
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.icon_options),
|
||||
contentDescription = null,
|
||||
tint = NTOTheme.colors.buttonDisabled
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
IconButton(
|
||||
modifier = Modifier.size(44.dp), onClick = {
|
||||
viewModel.logout(navController)
|
||||
}, colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = NTOTheme.colors.buttonDisabled
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.icon_logout),
|
||||
contentDescription = null,
|
||||
tint = NTOTheme.colors.primaryBackground
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.logo_placeholder_user),
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.clip(
|
||||
CircleShape
|
||||
),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(15.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(NTOTheme.colors.inputFieldBackground)
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Text(
|
||||
state.secondName,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
Text(
|
||||
state.firstName,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
Text(
|
||||
state.thirdName,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = 16.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
state.job,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontFamily = raleway,
|
||||
fontSize = 14.sp,
|
||||
color = TextGray
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(35.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(NTOTheme.colors.inputFieldBackground)
|
||||
.padding(start = 15.dp, end = 15.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.label_last_visit),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
Text(
|
||||
state.lastOpen,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Text(
|
||||
stringResource(R.string.label_visits),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 20.sp,
|
||||
fontFamily = raleway,
|
||||
color = NTOTheme.colors.primaryText
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(state.visits) { item ->
|
||||
VisitCard(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
DecoratedButton(
|
||||
stringResource(R.string.label_scan),
|
||||
false,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 10.dp)
|
||||
.fillMaxWidth()
|
||||
.height(62.dp)
|
||||
) {
|
||||
scannerLauncher.launch(scanOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ProfileScreenPreview() {
|
||||
NTOTheme {
|
||||
ProfileScreen(rememberNavController(), Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.nto.presentation.screens.profileScreen
|
||||
|
||||
import android.content.Context
|
||||
import com.nto.data.models.Position
|
||||
import com.nto.data.models.UserDTO
|
||||
import com.nto.data.models.cards.VisitCardDTO
|
||||
import com.nto.presentation.R
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
data class ProfileState(
|
||||
var firstName: String = "",
|
||||
var secondName: String = "",
|
||||
var thirdName: String = "",
|
||||
var lastOpen: String = "",
|
||||
var job: String = "",
|
||||
var isUnauthorized: Boolean = false,
|
||||
var visits: List<VisitCardDTO> = listOf()
|
||||
) {
|
||||
fun deserialize(o: UserDTO, context: Context) {
|
||||
val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
this.firstName = o.firstName
|
||||
this.secondName = o.lastName
|
||||
this.thirdName = o.patronymic
|
||||
this.lastOpen = try {
|
||||
o.lastVisit.format(dateFormat)
|
||||
} catch (e: NullPointerException) {
|
||||
context.getString(R.string.label_last_visit_none)
|
||||
}
|
||||
this.job = translatePosition(o.position, context)
|
||||
this.isUnauthorized = o.isUnauthorized
|
||||
}
|
||||
|
||||
private fun translatePosition(position: Position, context: Context): String {
|
||||
return when (position) {
|
||||
Position.TESTER -> context.getString(R.string.label_tester)
|
||||
Position.DEVELOPER -> context.getString(R.string.label_developer)
|
||||
Position.DESIGNER -> context.getString(R.string.label_designer)
|
||||
Position.ANALYST -> context.getString(R.string.label_analyst)
|
||||
Position.ADMINISTRATOR -> context.getString(R.string.label_administrator)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.nto.presentation.screens.profileScreen
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavController
|
||||
import com.nto.data.models.QRDTO
|
||||
import com.nto.data.models.cards.VisitCardDTO
|
||||
import com.nto.data.models.cards.VisitType
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.domain.usecase.ProfileUseCase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.invoke
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ProfileViewModel @Inject constructor(
|
||||
private val useCase: ProfileUseCase, @ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
private var _isLoading by mutableStateOf(false)
|
||||
private val _state = MutableStateFlow(ProfileState())
|
||||
|
||||
val isLoading get() = _isLoading
|
||||
val state: StateFlow<ProfileState>
|
||||
get() = _state.asStateFlow()
|
||||
|
||||
fun updateInfo() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_isLoading = true
|
||||
val result = useCase.getInfo()
|
||||
val visitsResult = useCase.getVisits()
|
||||
_state.tryEmit(ProfileState().apply {
|
||||
deserialize(result, context)
|
||||
if(!visitsResult.isError) {
|
||||
visits = visitsResult.data!!
|
||||
}
|
||||
})
|
||||
_isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
fun admin(navController: NavController) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
fun logout(navController: NavController) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
useCase.logout()
|
||||
Dispatchers.Main {
|
||||
navController.navigate(Destinations.Login) {
|
||||
popUpTo<Destinations.Profile> {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun option(navController: NavController) {
|
||||
navController.navigate(Destinations.Options)
|
||||
}
|
||||
|
||||
init {
|
||||
updateInfo()
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package com.nto.presentation.screens.scanResult
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.gestures.snapping.SnapPosition
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.DefaultShadowColor
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.nto.data.utils.Destinations
|
||||
import com.nto.presentation.R
|
||||
import com.nto.presentation.theme.NTOTheme
|
||||
import com.nto.presentation.theme.playfair
|
||||
import com.nto.presentation.theme.raleway
|
||||
|
||||
@Composable
|
||||
fun ScanResultScreen(
|
||||
navController: NavController,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: ScanResultViewModel = hiltViewModel()
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
Scaffold(bottomBar = {
|
||||
val buttonColor = when (state) {
|
||||
ScanResultState.Success -> NTOTheme.colors.button
|
||||
ScanResultState.Error -> NTOTheme.colors.buttonAdmin
|
||||
ScanResultState.Warning -> NTOTheme.colors.warning
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
navController.navigate(Destinations.Profile) {
|
||||
popUpTo<Destinations.Scan> {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = buttonColor,
|
||||
containerColor = Color.Transparent
|
||||
),
|
||||
border = BorderStroke(width = 2.dp, color = buttonColor),
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 32.dp)
|
||||
.fillMaxWidth()
|
||||
.height(62.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.close),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}) { paddingValues ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(
|
||||
id = when (state) {
|
||||
ScanResultState.Success -> R.drawable.ic_scan_success
|
||||
ScanResultState.Error -> R.drawable.ic_scan_error
|
||||
ScanResultState.Warning -> R.drawable.ic_scan_warning
|
||||
}
|
||||
), contentDescription = ""
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.code_scanned),
|
||||
fontFamily = playfair,
|
||||
fontSize = 36.sp,
|
||||
color = NTOTheme.colors.primaryText
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
when (state) {
|
||||
ScanResultState.Success -> R.string.code_scanned_success
|
||||
ScanResultState.Error -> R.string.code_scanned_error
|
||||
ScanResultState.Warning -> R.string.code_scanned_warning
|
||||
}
|
||||
),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = NTOTheme.colors.primaryText,
|
||||
fontFamily = raleway
|
||||
)
|
||||
Spacer(Modifier.height(60.dp))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.nto.presentation.screens.scanResult
|
||||
|
||||
enum class ScanResultState {
|
||||
Success, Error, Warning
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.nto.presentation.screens.scanResult
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.toRoute
|
||||
import com.nto.data.utils.Destinations
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
@HiltViewModel
|
||||
class ScanResultViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle
|
||||
): ViewModel() {
|
||||
val value = savedStateHandle.toRoute<Destinations.Scan>().value
|
||||
private val _state = MutableStateFlow(ScanResultState.Success)
|
||||
val state get() = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
// TODO: create method to scan qr on server
|
||||
_state.value = when (Random.nextInt(0, 3)) {
|
||||
0 -> ScanResultState.Success
|
||||
1 -> ScanResultState.Error
|
||||
2 -> ScanResultState.Warning
|
||||
else -> ScanResultState.Warning
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.nto.presentation.screens.splashScreen
|
||||
|
||||
data class SplashScreenState(
|
||||
val skipAuth: Boolean = false
|
||||
)
|
@ -0,0 +1,31 @@
|
||||
package com.nto.presentation.screens.splashScreen
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nto.domain.usecase.SplashScreenUseCase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SplashScreenViewModel @Inject constructor(private val useCase: SplashScreenUseCase) : ViewModel(){
|
||||
private val _state = MutableStateFlow(
|
||||
SplashScreenState()
|
||||
)
|
||||
|
||||
val state: StateFlow<SplashScreenState>
|
||||
get() = _state.asStateFlow()
|
||||
|
||||
fun checkLogin(){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val token = useCase.getToken()
|
||||
if (!token.isNullOrBlank()){
|
||||
_state.tryEmit(_state.value.copy(skipAuth = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,12 @@ import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
||||
val TextGray = Color(0xFFC4BBC7)
|
||||
val BoxGray = Color(0xFFF8F0FB)
|
||||
val Background = Color(0xFFFEFBFF)
|
||||
val Green = Color(0xFF738D73)
|
||||
val GreenDisabled = Color(0xFFCAD5CA)
|
||||
val Error = Color(0xFFD28989)
|
||||
val Warning = Color(0xFFCFC37F)
|
||||
val TextColor = Color(0xFF211A1D)
|
||||
|
||||
@Immutable
|
||||
data class AppColors(
|
||||
@ -20,7 +24,9 @@ data class AppColors(
|
||||
val secondaryText: Color,
|
||||
val button: Color,
|
||||
val buttonDisabled: Color,
|
||||
val buttonAdmin: Color,
|
||||
val tint: Color,
|
||||
val warning: Color,
|
||||
|
||||
)
|
||||
|
||||
@ -35,18 +41,22 @@ val LocalAppColors = staticCompositionLocalOf {
|
||||
secondaryText = Color.Unspecified,
|
||||
button = Color.Unspecified,
|
||||
buttonDisabled = Color.Unspecified,
|
||||
tint = Color.Unspecified
|
||||
buttonAdmin = Color.Unspecified,
|
||||
tint = Color.Unspecified,
|
||||
warning = Color.Unspecified
|
||||
)
|
||||
}
|
||||
|
||||
val extendedColor = AppColors(
|
||||
primaryBackground = Color.White,
|
||||
primaryBackground = Background,
|
||||
secondaryBackground = Color.Black,
|
||||
inputFieldBackground = BoxGray,
|
||||
disabledText = TextGray,
|
||||
primaryText = Color.Black,
|
||||
primaryText = TextColor,
|
||||
secondaryText = Color.White,
|
||||
button = Green,
|
||||
buttonDisabled = GreenDisabled,
|
||||
tint = Color.Black
|
||||
buttonAdmin = Error,
|
||||
tint = Color.Black,
|
||||
warning = Warning
|
||||
)
|
@ -3,13 +3,34 @@ package com.nto.presentation.theme
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.text.ExperimentalTextApi
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontVariation
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.nto.presentation.R
|
||||
|
||||
@OptIn(ExperimentalTextApi::class)
|
||||
val raleway = FontFamily(
|
||||
Font(R.font.raleway, FontWeight.SemiBold, variationSettings = FontVariation.Settings(
|
||||
FontVariation.weight(FontWeight.SemiBold.weight)
|
||||
)),
|
||||
Font(R.font.raleway, FontWeight.Medium, variationSettings = FontVariation.Settings(
|
||||
FontVariation.weight(FontWeight.Medium.weight)
|
||||
)),
|
||||
Font(R.font.raleway, FontWeight.Normal, variationSettings = FontVariation.Settings(
|
||||
FontVariation.weight(FontWeight.Normal.weight)
|
||||
))
|
||||
)
|
||||
@OptIn(ExperimentalTextApi::class)
|
||||
val playfair = FontFamily(
|
||||
Font(R.font.playfair, FontWeight.Bold, variationSettings = FontVariation.Settings(
|
||||
FontVariation.weight(FontWeight.Bold.weight)
|
||||
))
|
||||
)
|
||||
|
||||
private val RalewayFontFamily = FontFamily(
|
||||
Font(R.font.raleway)
|
||||
)
|
||||
|
9
presentation/src/main/res/drawable/ic_arrow_back.xml
Normal file
9
presentation/src/main/res/drawable/ic_arrow_back.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M7.825,13L13.425,18.6L12,20L4,12L12,4L13.425,5.4L7.825,11H20V13H7.825Z"
|
||||
android:fillColor="#CAD5CA"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/ic_scan_error.xml
Normal file
9
presentation/src/main/res/drawable/ic_scan_error.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="228dp"
|
||||
android:height="228dp"
|
||||
android:viewportWidth="228"
|
||||
android:viewportHeight="228">
|
||||
<path
|
||||
android:pathData="M79.8,161.5L114,127.3L148.2,161.5L161.5,148.2L127.3,114L161.5,79.8L148.2,66.5L114,100.7L79.8,66.5L66.5,79.8L100.7,114L66.5,148.2L79.8,161.5ZM114,209C100.86,209 88.51,206.51 76.95,201.52C65.39,196.53 55.34,189.76 46.79,181.21C38.24,172.66 31.47,162.61 26.48,151.05C21.49,139.49 19,127.14 19,114C19,100.86 21.49,88.51 26.48,76.95C31.47,65.39 38.24,55.34 46.79,46.79C55.34,38.24 65.39,31.47 76.95,26.48C88.51,21.49 100.86,19 114,19C127.14,19 139.49,21.49 151.05,26.48C162.61,31.47 172.66,38.24 181.21,46.79C189.76,55.34 196.53,65.39 201.52,76.95C206.51,88.51 209,100.86 209,114C209,127.14 206.51,139.49 201.52,151.05C196.53,162.61 189.76,172.66 181.21,181.21C172.66,189.76 162.61,196.53 151.05,201.52C139.49,206.51 127.14,209 114,209ZM114,190C135.22,190 153.19,182.64 167.91,167.91C182.64,153.19 190,135.22 190,114C190,92.78 182.64,74.81 167.91,60.09C153.19,45.36 135.22,38 114,38C92.78,38 74.81,45.36 60.09,60.09C45.36,74.81 38,92.78 38,114C38,135.22 45.36,153.19 60.09,167.91C74.81,182.64 92.78,190 114,190Z"
|
||||
android:fillColor="#D28989"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/ic_scan_success.xml
Normal file
9
presentation/src/main/res/drawable/ic_scan_success.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="228dp"
|
||||
android:height="228dp"
|
||||
android:viewportWidth="228"
|
||||
android:viewportHeight="228">
|
||||
<path
|
||||
android:pathData="M63.65,171L9.97,117.32L23.51,104.03L63.89,144.4L77.19,157.7L63.65,171ZM117.32,171L63.65,117.32L76.95,103.79L117.32,144.16L204.73,56.76L218.02,70.3L117.32,171ZM117.32,117.32L103.79,104.03L150.81,57L164.35,70.3L117.32,117.32Z"
|
||||
android:fillColor="#738D73"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/ic_scan_warning.xml
Normal file
9
presentation/src/main/res/drawable/ic_scan_warning.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="228dp"
|
||||
android:height="228dp"
|
||||
android:viewportWidth="228"
|
||||
android:viewportHeight="228">
|
||||
<path
|
||||
android:pathData="M28.5,190V171H54.63L50.83,167.68C42.59,160.39 36.81,152.08 33.49,142.74C30.16,133.4 28.5,123.97 28.5,114.47C28.5,96.9 33.76,81.26 44.29,67.57C54.82,53.87 68.56,44.81 85.5,40.38V60.33C74.1,64.44 64.92,71.45 57.95,81.34C50.98,91.24 47.5,102.28 47.5,114.47C47.5,121.6 48.85,128.53 51.54,135.26C54.23,141.99 58.42,148.2 64.13,153.9L66.5,156.27V133H85.5V190H28.5ZM114,161.5C111.31,161.5 109.05,160.59 107.23,158.77C105.41,156.95 104.5,154.69 104.5,152C104.5,149.31 105.41,147.05 107.23,145.23C109.05,143.41 111.31,142.5 114,142.5C116.69,142.5 118.95,143.41 120.77,145.23C122.59,147.05 123.5,149.31 123.5,152C123.5,154.69 122.59,156.95 120.77,158.77C118.95,160.59 116.69,161.5 114,161.5ZM104.5,123.5V66.5H123.5V123.5H104.5ZM142.5,187.63V167.68C153.9,163.56 163.08,156.55 170.05,146.66C177.02,136.76 180.5,125.72 180.5,113.53C180.5,106.4 179.15,99.47 176.46,92.74C173.77,86.01 169.57,79.8 163.88,74.1L161.5,71.72V95H142.5V38H199.5V57H173.38L177.18,60.33C184.93,68.08 190.59,76.51 194.16,85.62C197.72,94.72 199.5,104.03 199.5,113.53C199.5,131.1 194.24,146.74 183.71,160.43C173.18,174.13 159.44,183.19 142.5,187.63Z"
|
||||
android:fillColor="#CFC37F"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/icon_admin.xml
Normal file
9
presentation/src/main/res/drawable/icon_admin.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M12.279,13.302C11.273,13.302 10.424,12.957 9.734,12.266C9.043,11.576 8.698,10.727 8.698,9.721C8.698,8.715 9.043,7.866 9.734,7.176C10.424,6.485 11.273,6.14 12.279,6.14C13.285,6.14 14.134,6.485 14.824,7.176C15.515,7.866 15.861,8.715 15.861,9.721C15.861,10.727 15.515,11.576 14.824,12.266C14.134,12.957 13.285,13.302 12.279,13.302ZM12.279,11.256C12.722,11.256 13.089,11.111 13.379,10.821C13.669,10.531 13.814,10.164 13.814,9.721C13.814,9.278 13.669,8.911 13.379,8.621C13.089,8.331 12.722,8.186 12.279,8.186C11.836,8.186 11.469,8.331 11.179,8.621C10.889,8.911 10.744,9.278 10.744,9.721C10.744,10.164 10.889,10.531 11.179,10.821C11.469,11.111 11.836,11.256 12.279,11.256ZM12.279,22.512C9.909,21.915 7.952,20.555 6.408,18.431C4.865,16.308 4.093,13.95 4.093,11.358V5.116L12.279,2.047L20.465,5.116V11.358C20.465,13.95 19.693,16.308 18.15,18.431C16.607,20.555 14.65,21.915 12.279,22.512ZM12.279,4.221L6.14,6.523V11.358C6.14,12.279 6.267,13.174 6.523,14.044C6.779,14.914 7.129,15.733 7.572,16.5C8.288,16.142 9.039,15.861 9.823,15.656C10.608,15.451 11.426,15.349 12.279,15.349C13.132,15.349 13.95,15.451 14.735,15.656C15.519,15.861 16.27,16.142 16.986,16.5C17.43,15.733 17.779,14.914 18.035,14.044C18.291,13.174 18.419,12.279 18.419,11.358V6.523L12.279,4.221ZM12.279,17.395C11.665,17.395 11.068,17.464 10.488,17.6C9.909,17.736 9.354,17.924 8.826,18.163C9.32,18.674 9.857,19.118 10.437,19.493C11.017,19.868 11.631,20.158 12.279,20.363C12.927,20.158 13.541,19.868 14.121,19.493C14.701,19.118 15.238,18.674 15.733,18.163C15.204,17.924 14.65,17.736 14.07,17.6C13.49,17.464 12.893,17.395 12.279,17.395Z"
|
||||
android:fillColor="#D28989"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/icon_logout.xml
Normal file
9
presentation/src/main/res/drawable/icon_logout.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M5,21C4.45,21 3.979,20.804 3.588,20.413C3.196,20.021 3,19.55 3,19V5C3,4.45 3.196,3.979 3.588,3.588C3.979,3.196 4.45,3 5,3H12V5H5V19H12V21H5ZM16,17L14.625,15.55L17.175,13H9V11H17.175L14.625,8.45L16,7L21,12L16,17Z"
|
||||
android:fillColor="#FEFBFF"/>
|
||||
</vector>
|
9
presentation/src/main/res/drawable/icon_options.xml
Normal file
9
presentation/src/main/res/drawable/icon_options.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="25dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="25"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M9.25,22L8.85,18.8C8.633,18.717 8.429,18.617 8.238,18.5C8.046,18.383 7.858,18.258 7.675,18.125L4.7,19.375L1.95,14.625L4.525,12.675C4.508,12.558 4.5,12.446 4.5,12.337V11.663C4.5,11.554 4.508,11.442 4.525,11.325L1.95,9.375L4.7,4.625L7.675,5.875C7.858,5.742 8.05,5.617 8.25,5.5C8.45,5.383 8.65,5.283 8.85,5.2L9.25,2H14.75L15.15,5.2C15.367,5.283 15.571,5.383 15.762,5.5C15.954,5.617 16.142,5.742 16.325,5.875L19.3,4.625L22.05,9.375L19.475,11.325C19.492,11.442 19.5,11.554 19.5,11.663V12.337C19.5,12.446 19.483,12.558 19.45,12.675L22.025,14.625L19.275,19.375L16.325,18.125C16.142,18.258 15.95,18.383 15.75,18.5C15.55,18.617 15.35,18.717 15.15,18.8L14.75,22H9.25ZM11,20H12.975L13.325,17.35C13.842,17.217 14.321,17.021 14.762,16.763C15.204,16.504 15.608,16.192 15.975,15.825L18.45,16.85L19.425,15.15L17.275,13.525C17.358,13.292 17.417,13.046 17.45,12.788C17.483,12.529 17.5,12.267 17.5,12C17.5,11.733 17.483,11.471 17.45,11.212C17.417,10.954 17.358,10.708 17.275,10.475L19.425,8.85L18.45,7.15L15.975,8.2C15.608,7.817 15.204,7.496 14.762,7.238C14.321,6.979 13.842,6.783 13.325,6.65L13,4H11.025L10.675,6.65C10.158,6.783 9.679,6.979 9.238,7.238C8.796,7.496 8.392,7.808 8.025,8.175L5.55,7.15L4.575,8.85L6.725,10.45C6.642,10.7 6.583,10.95 6.55,11.2C6.517,11.45 6.5,11.717 6.5,12C6.5,12.267 6.517,12.525 6.55,12.775C6.583,13.025 6.642,13.275 6.725,13.525L4.575,15.15L5.55,16.85L8.025,15.8C8.392,16.183 8.796,16.504 9.238,16.763C9.679,17.021 10.158,17.217 10.675,17.35L11,20ZM12.05,15.5C13.017,15.5 13.842,15.158 14.525,14.475C15.208,13.792 15.55,12.967 15.55,12C15.55,11.033 15.208,10.208 14.525,9.525C13.842,8.842 13.017,8.5 12.05,8.5C11.067,8.5 10.238,8.842 9.563,9.525C8.888,10.208 8.55,11.033 8.55,12C8.55,12.967 8.888,13.792 9.563,14.475C10.238,15.158 11.067,15.5 12.05,15.5Z"
|
||||
android:fillColor="#CAD5CA"/>
|
||||
</vector>
|
BIN
presentation/src/main/res/drawable/logo_placeholder_admin.png
Normal file
BIN
presentation/src/main/res/drawable/logo_placeholder_admin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
presentation/src/main/res/drawable/logo_placeholder_user.png
Normal file
BIN
presentation/src/main/res/drawable/logo_placeholder_user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
Binary file not shown.
@ -1,4 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">presentation</string>
|
||||
<string name="app_name">SKUF Company</string>
|
||||
<string name="action_login">Sign in</string>
|
||||
<string name="text_login">Login</string>
|
||||
<string name="text_password">Password</string>
|
||||
<string name="greeting_login_description">Sign to your account to continue</string>
|
||||
<string name="greeting_login">Sign in</string>
|
||||
<string name="title_profile">Profile</string>
|
||||
<string name="label_qr_login">Enter by code</string>
|
||||
<string name="label_card_login">Enter by card</string>
|
||||
<string name="label_last_visit">Last visit:</string>
|
||||
<string name="label_visits">Visits</string>
|
||||
<string name="label_scan">Scan QR</string>
|
||||
<string name="label_tester">Tester</string>
|
||||
<string name="label_developer">Developer</string>
|
||||
<string name="label_designer">Designer</string>
|
||||
<string name="label_analyst">Analytics</string>
|
||||
<string name="label_administrator">Administrator</string>
|
||||
<string name="code_scanned">Code scanned</string>
|
||||
<string name="code_scanned_success">Success</string>
|
||||
<string name="code_scanned_error">Enter was cancelled</string>
|
||||
<string name="code_scanned_warning">Something went wrong</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="label_last_visit_none">None</string>
|
||||
<string name="admin_header">Admin</string>
|
||||
</resources>
|
@ -1,10 +1,28 @@
|
||||
<resources>
|
||||
<string name="app_name">presentation</string>
|
||||
<string name="login_button">Войти</string>
|
||||
<string name="text_email">Почта</string>
|
||||
<string name="app_name">СКУД</string>
|
||||
<string name="action_login">Войти</string>
|
||||
<string name="text_login">Логин</string>
|
||||
<string name="text_password">Пароль</string>
|
||||
<string name="greeting_login_description">Войдите в свой аккаунт чтобы продолжить</string>
|
||||
<string name="greeting_login">Вход</string>
|
||||
<string name="placholder_email" translatable="false">example@mail.com</string>
|
||||
<string name="placholder_login" translatable="false">\@pivanov</string>
|
||||
<string name="placeholder_password" translatable="false">**********</string>
|
||||
<string name="title_profile">Профиль</string>
|
||||
<string name="label_qr_login">Вход по коду</string>
|
||||
<string name="label_card_login">Вход по карте</string>
|
||||
<string name="label_last_visit">Последний вход:</string>
|
||||
<string name="label_visits">Посещения</string>
|
||||
<string name="label_scan">Сканировать код</string>
|
||||
<string name="label_tester">Тестировщик</string>
|
||||
<string name="label_developer">Разработчик</string>
|
||||
<string name="label_designer">Дизайнер</string>
|
||||
<string name="label_analyst">Аналитик</string>
|
||||
<string name="label_administrator">Администратор</string>
|
||||
<string name="code_scanned">Код отсканирован</string>
|
||||
<string name="code_scanned_success">Успешно</string>
|
||||
<string name="code_scanned_error">Вход был отменён</string>
|
||||
<string name="code_scanned_warning">Что-то пошло не так</string>
|
||||
<string name="close">Закрыть</string>
|
||||
<string name="admin_header">Управление</string>
|
||||
<string name="label_last_visit_none">Нет</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user