add authorization and LoginFragment

This commit is contained in:
varksyu 2025-02-18 20:34:13 +03:00
parent 1ac20aec4f
commit b6e9c35ba4
26 changed files with 636 additions and 17 deletions

View File

@ -47,11 +47,18 @@ dependencies {
implementation(Dependencies.Retrofit.library)
implementation(Dependencies.Retrofit.gsonConverter)
implementation("com.squareup.picasso:picasso:2.8")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.google.mlkit:barcode-scanning:17.3.0")
implementation ("io.ktor:ktor-client-core:2.3.5")
implementation ("io.ktor:ktor-client-cio:2.3.5")
implementation ("io.ktor:ktor-client-content-negotiation:2.3.5")
implementation ("io.ktor:ktor-serialization-kotlinx-json:2.3.5")
val cameraX = "1.3.4"
implementation("androidx.camera:camera-core:$cameraX")
implementation("androidx.camera:camera-camera2:$cameraX")

View File

@ -19,15 +19,23 @@
android:supportsRtl="true"
android:theme="@style/Theme.Default"
tools:targetApi="31">
<activity
android:name=".ui.login.EntryActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.RootActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,44 @@
package ru.myitschool.work.data.auth
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import ru.myitschool.work.core.Constants.SERVER_ADDRESS
import ru.myitschool.work.data.auth.Network.client
import ru.myitschool.work.data.user.UserDto
object AuthNetworkDataSource {
suspend fun isUserExist(login: String): Result<Boolean?> = withContext(Dispatchers.IO) {
runCatching {
val result = client.get("$SERVER_ADDRESS/api/user/login/$login") //10.0.2.2
when (result.status) {
HttpStatusCode.OK -> { return@runCatching true }
HttpStatusCode.NotFound -> { return@runCatching false }
else -> {return@runCatching null }
}
}
}
suspend fun login(token: String): Result<UserDto> = withContext(Dispatchers.IO) {
runCatching {
val result = client.get("$SERVER_ADDRESS/api/user/login") {
header(HttpHeaders.Authorization, token)
}
if (result.status == HttpStatusCode.Unauthorized) {
error("Неверный email или пароль")
}
result.body<UserDto>()
}
}
}

View File

@ -0,0 +1,27 @@
package ru.myitschool.work.data.auth
import ru.myitschool.work.data.user.UserDto
import ru.sicampus.bootcamp2025.domain.auth.AuthRepo
class AuthRepoImpl(
private val authNetworkDataSource: AuthNetworkDataSource,
private val authStorageDataSource: AuthStorageDataSource
) : AuthRepo{
override suspend fun isUserExist(email: String): Result<Boolean?> {
return authNetworkDataSource.isUserExist(email)
}
override suspend fun login(email: String, password: String): Result<UserDto> {
val token = authStorageDataSource.updateToken(email, password)
val userInfo = authNetworkDataSource.login(token).onFailure {
authStorageDataSource.clear()
}
if (userInfo.isSuccess){
authStorageDataSource.updateUserInfo(userInfo)
}
return userInfo
}
}

View File

@ -0,0 +1,30 @@
package ru.myitschool.work.data.auth
import okhttp3.Credentials
import ru.myitschool.work.data.user.UserDto
object AuthStorageDataSource {
var token: String? = null
private set
var userInfo : UserDto? = null
fun updateToken(email : String, password : String) : String {
val updateToken = Credentials.basic(email, password)
token = updateToken
return updateToken
}
fun updateUserInfo(userDto: Result<UserDto>) {
userDto.onSuccess { user ->
userInfo = user
}.onFailure { error ->
userInfo = null
error("Server Error id = null")
}
}
fun clear() {
token = null
userInfo = null
}
}

View File

@ -0,0 +1,18 @@
package ru.myitschool.work.data.auth
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
object Network {
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
isLenient = true
ignoreUnknownKeys = true
})
}
}
}

View File

@ -0,0 +1,39 @@
package ru.myitschool.work.data.user
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import ru.myitschool.work.domain.user.UserEntity
import java.sql.Timestamp
@Serializable
data class UserDto(
@SerialName("id")
val id : Long?,
@SerialName("login")
var login: String,
@SerialName("birthDate")
var birthDate : String?,
@SerialName("name")
var name: String,
@SerialName("avatarUrl")
var avatarUrl: String?,
@SerialName("position")
val position: String,
@SerialName("lastEntry")
val lastEntry : String,
@SerialName("authorities")
val authorities : String
) {
fun toEntity(): UserEntity {
return UserEntity(
id = id ?: throw IllegalArgumentException("User ID cannot be null"),
login,
name = name,
avatarUrl = avatarUrl,
position = position,
lastEntry = lastEntry,
authorities = authorities
)
}
}

View File

@ -0,0 +1,14 @@
package ru.myitschool.work.domain.user
import java.sql.Timestamp
data class UserEntity(
val id : Long,
var login: String,
var name: String,
var avatarUrl: String?,
val position : String,
var lastEntry : String,
val authorities : String
)

View File

@ -21,7 +21,7 @@ class RootActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_root)
val navHostFragment = supportFragmentManager
/*val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
if (navHostFragment != null) {
@ -52,5 +52,6 @@ class RootActivity : AppCompatActivity() {
false
}
return popBackResult || super.onSupportNavigateUp()
}*/
}
}

View File

@ -0,0 +1,20 @@
package ru.myitschool.work.ui.login
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
@AndroidEntryPoint
class EntryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_entry)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
}
}

View File

@ -1,6 +1,10 @@
package ru.myitschool.work.ui.login
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Patterns
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@ -12,25 +16,68 @@ import ru.myitschool.work.utils.visibleOrGone
@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) {
private var _binding: FragmentLoginBinding? = null
private val binding: FragmentLoginBinding get() = _binding!!
private var _viewBinding: FragmentLoginBinding? = null
private val viewBinding: FragmentLoginBinding get() = _viewBinding!!
private val viewModel: LoginViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentLoginBinding.bind(view)
subscribe()
}
_viewBinding = FragmentLoginBinding.bind(view)
private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state ->
binding.loading.visibleOrGone(state)
/*viewBinding.signInButton.setOnClickListener {
val login = viewBinding.userLogin.text.toString()
val password = viewBinding.userPassword.text.toString()
if (!isValidEmail(login)) {
viewBinding.errorText.text = getString(R.string.error_valid)
viewBinding.errorText.visibility = View.VISIBLE
}
else if (!isValidPassword(password)) {
viewBinding.errorText.text = getString(R.string.error_valid)
viewBinding.errorText.visibility = View.VISIBLE
}
else {
viewModel.auth(email, password)
viewBinding.errorText.visibility = View.GONE
}
}
viewModel.state.collectWithLifecycle(this) { state ->
if (state is AuthViewModel.State.Show) {
viewBinding.errorText.text = state.errorText.toString()
viewBinding.errorText.visibility =
if (state.errorText == null) View.GONE else View.VISIBLE
}
}
viewModel.navigateToMain.collectWithLifecycle(viewLifecycleOwner) { userRole ->
val intent = Intent(requireContext(), MainActivity::class.java).apply {
putExtra("USER_ROLE", userRole)
}
startActivity(intent)
requireActivity().finish()
}
viewBinding.userLogin.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) = Unit
override fun afterTextChanged(p0: Editable?) {
}
})*/
}
private fun isValidEmail(email: String): Boolean {
return Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
private fun isValidPassword(password : String) : Boolean {
return password.length >= 8
}
override fun onDestroyView() {
_binding = null
_viewBinding = null
super.onDestroyView()
}
}

View File

@ -1,17 +1,132 @@
package ru.myitschool.work.ui.login
import LoginUseCase
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import ru.myitschool.work.R
import ru.myitschool.work.domain.auth.IsUserExistUseCase
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val isUserExistUseCase: IsUserExistUseCase,
private val loginUseCase: LoginUseCase
) : ViewModel() {
private val _state = MutableStateFlow(true)
val state = _state.asStateFlow()
private val _navigateToMain = MutableSharedFlow<String?>()
val navigateToMain = _navigateToMain.asSharedFlow()
private val _userRole = MutableSharedFlow<String>()
val userRole = _userRole.asSharedFlow()
/*
init {
viewModelScope.launch {
updateState()
}
}
fun auth(
email : String,
password : String
)
{
viewModelScope.launch {
_state.emit(State.Loading)
when (checkUserExistence(email)) {
true -> {
loginUser(email, password)
}
false -> {
updateState(context.getString(R.string.error_invalid_credentials))
}
null -> updateState(context.getString(R.string.error_unknown))
}
}
}
private suspend fun checkUserExistence(email: String):Boolean?{
return try {
val result = isUserExistUseCase(email)
result.fold(
onSuccess = {isExist -> isExist},
onFailure = {
Log.e("AuthViewModel", "Error checking user existence", it)
null
}
)
} catch (e: Exception) {
Log.e("AuthViewModel", "Error during user existence check", e)
null
}
}
private suspend fun loginUser(email: String, password: String) {
loginUseCase(email, password).fold(
onSuccess = { user ->
println("Login successful")
_userRole.emit(user.authorities)
_navigateToMain.emit(user.authorities)
},
onFailure = { error ->
updateState(error.message ?: context.getString(R.string.error_unknown))
}
)
}
private suspend fun updateState(error : String? = null) {
_state.emit(getStateShow(error))
}
private fun getStateShow(error : String? = null) : State {
return State.Show(
errorText = error
)
}
fun changeLogin() {
viewModelScope.launch {
updateState()
}
}
sealed interface State {
data object Loading : State
data class Show(
var errorText : String?
) : State
}
*/
/*companion object {
val Factory : ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: KClass<T>, extras: CreationExtras): T {
val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]!!
val authRepoImpl = AuthRepoImpl(
authNetworkDataSource = AuthNetworkDataSource,
authStorageDataSource = AuthStorageDataSource
)
return AuthViewModel(
application = application,
isUserExistUseCase = IsUserExistUseCase(authRepoImpl),
loginUseCase = LoginUseCase(authRepoImpl)
) as T
}
}
}*/
}

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="64dp"
android:viewportWidth="56"
android:viewportHeight="64">
<path
android:pathData="M27.6,7C27.6,7.2 27.5,7.3 27.5,7.5C26.8,9.9 24.2,12.8 19.3,16.8C19.2,17 19.1,17 19,17C18.9,17.1 18.7,17.1 18.6,17.1C18.4,17.1 18.2,17 18,16.9C11.8,11.9 9,8.1 9.5,5.2C9.5,5.1 9.5,5.1 9.5,5C9.9,2.5 12.1,0.6 14.7,0.6C16.2,0.6 17.6,1.2 18.6,2.3C19.6,1.2 21,0.6 22.5,0.6C25.1,0.6 27.3,2.5 27.7,5C27.7,5.1 27.8,5.2 27.8,5.3C27.7,5.9 27.7,6.5 27.6,7Z"
android:fillColor="#9F27FE"/>
<path
android:pathData="M28.8,42.3C28.9,42.1 28.9,42 28.9,41.8V28.6C28.9,27.7 28.6,26.9 28,26.3C27.1,25.4 25.6,25.1 24.5,25.6V24.5C24.5,23.6 24.2,22.8 23.6,22.2C22.6,21.2 21.1,21 20,21.5C19.9,21 19.6,20.5 19.2,20.1C18.6,19.5 17.8,19.1 16.9,19.1C15.4,19.1 14.1,20.2 13.8,21.6C13.4,21.4 12.9,21.3 12.5,21.3C10.7,21.3 9.3,22.7 9.3,24.5V37.9L5.2,35C4,33.9 1.9,34.1 0.8,35.3C0.2,35.9 0,36.7 0,37.5C0,38.3 0.4,39.1 1,39.7L9.2,46.8L6.8,62.6C6.8,62.9 6.8,63.2 7,63.4C7.2,63.6 7.5,63.7 7.8,63.7H29.6C29.9,63.7 30.2,63.6 30.4,63.3C30.6,63.1 30.7,62.8 30.6,62.5L27.1,45.1L28.8,42.3ZM28.4,61.7H8.9L11,46.7C11,46.4 10.9,46 10.7,45.8L2.1,38.3C1.9,38.1 1.8,37.8 1.8,37.5C1.8,37.2 1.9,36.9 2.1,36.7C2.5,36.3 3.2,36.2 3.7,36.6C3.7,36.6 3.8,36.6 3.8,36.7L9.5,40.8L11.6,42.1C11.8,42.2 11.9,42.2 12.1,42.2C12.4,42.2 12.8,42 13,41.7C13.3,41.2 13.1,40.6 12.7,40.3L11,39.3V24.6C11,23.9 11.6,23.4 12.2,23.4C12.6,23.4 12.9,23.6 13.1,23.8C13.3,24 13.5,24.3 13.5,24.7V35.4C13.5,36 13.9,36.4 14.5,36.4C15.1,36.4 15.5,36 15.5,35.4V24.6V22.4C15.5,21.7 16.1,21.2 16.7,21.2C17,21.2 17.3,21.3 17.6,21.6C17.8,21.8 18,22.1 18,22.5V24.6C18,24.6 18,24.6 18,24.7C18,24.7 18,24.7 18,24.8V35.5C18,36.1 18.4,36.5 19,36.5C19.6,36.5 20,36.1 20,35.5V24.6C20,24.6 20,24.6 20,24.5C20,24.5 20,24.5 20,24.4C20,23.7 20.6,23.2 21.2,23.2C21.6,23.2 21.9,23.4 22.1,23.6C22.3,23.8 22.5,24.1 22.5,24.5V28.6V35.3C22.5,35.9 22.9,36.3 23.5,36.3C24.1,36.3 24.5,35.9 24.5,35.3V28.6C24.5,27.9 25.1,27.4 25.7,27.4C26,27.4 26.3,27.5 26.6,27.8C26.8,28 27,28.3 27,28.7V41.6L25.3,44.5C25.2,44.7 25.1,45 25.2,45.2L28.4,61.7Z"
android:fillColor="#FF6900"/>
<path
android:pathData="M55.439,62.9C55.439,63.2 55.339,63.4 55.139,63.7C55.039,63.9 54.739,64 54.439,64H32.639C32.339,64 32.139,63.9 31.939,63.7C31.739,63.5 31.639,63.2 31.639,63L33.839,27.8L25.639,20.6C25.039,20 24.639,19.3 24.639,18.4C24.639,17.6 24.839,16.8 25.439,16.2C26.539,15 28.539,14.9 29.839,15.9L33.939,18.9V5.5C33.939,3.7 35.439,2.3 37.139,2.3C37.639,2.3 38.039,2.4 38.439,2.6C38.739,1.1 40.039,0 41.639,0C42.539,0 43.339,0.4 43.939,1C44.339,1.4 44.639,1.9 44.739,2.4C45.839,1.8 47.439,2.1 48.339,3C48.939,3.6 49.239,4.4 49.239,5.3V6.4C50.439,6 51.939,6.2 52.839,7.1C53.439,7.7 53.739,8.5 53.739,9.4V22.6C53.739,22.8 53.639,23 53.639,23.1L51.939,26L55.439,62.9Z"
android:fillColor="#FF6900"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="35dp"
android:height="35dp"
android:viewportWidth="35"
android:viewportHeight="35">
<group>
<clip-path
android:pathData="M0,0h35v35h-35z"/>
<path
android:pathData="M17.576,0.342C8.099,0.342 0.418,8.023 0.418,17.5C0.418,26.977 8.099,34.658 17.576,34.658C27.053,34.658 34.734,26.977 34.734,17.5C34.734,8.023 27.052,0.342 17.576,0.342ZM17.576,1.772C26.248,1.772 33.304,8.827 33.304,17.5C33.304,21.315 31.937,24.817 29.668,27.544C28.1,26.889 24.398,25.604 22.107,24.928C21.911,24.866 21.881,24.856 21.881,24.043C21.881,23.371 22.157,22.695 22.427,22.122C22.718,21.5 23.065,20.455 23.189,19.516C23.537,19.113 24.01,18.317 24.314,16.8C24.581,15.463 24.457,14.976 24.279,14.519C24.261,14.471 24.242,14.424 24.228,14.376C24.161,14.063 24.253,12.434 24.482,11.17C24.64,10.303 24.441,8.459 23.248,6.934C22.494,5.97 21.051,4.786 18.416,4.622L16.971,4.623C14.381,4.786 12.937,5.97 12.183,6.934C10.989,8.459 10.791,10.303 10.949,11.17C11.179,12.434 11.27,14.063 11.204,14.37C11.19,14.424 11.171,14.471 11.151,14.519C10.975,14.976 10.85,15.463 11.118,16.8C11.421,18.317 11.894,19.113 12.243,19.516C12.366,20.455 12.712,21.5 13.006,22.122C13.219,22.578 13.32,23.197 13.32,24.072C13.32,24.886 13.289,24.896 13.106,24.954C10.736,25.653 6.965,27.016 5.559,27.632C3.245,24.893 1.847,21.357 1.847,17.5C1.847,8.827 8.903,1.772 17.576,1.772ZM6.595,28.746C8.205,28.089 11.415,26.944 13.524,26.321C14.75,25.934 14.75,24.902 14.75,24.072C14.75,23.385 14.702,22.371 14.3,21.514C14.023,20.927 13.708,19.92 13.638,19.132C13.623,18.947 13.536,18.777 13.396,18.655C13.194,18.478 12.782,17.829 12.519,16.519C12.311,15.482 12.399,15.255 12.485,15.036C12.521,14.943 12.556,14.85 12.584,14.746C12.756,14.119 12.564,12.057 12.356,10.913C12.266,10.416 12.38,9.004 13.31,7.814C14.144,6.748 15.406,6.154 17.016,6.051L18.372,6.05C20.025,6.154 21.287,6.748 22.122,7.814C23.052,9.004 23.165,10.416 23.074,10.914C22.868,12.057 22.675,14.119 22.847,14.746C22.876,14.85 22.91,14.943 22.946,15.036C23.031,15.255 23.119,15.482 22.912,16.519C22.65,17.829 22.237,18.478 22.034,18.655C21.896,18.777 21.809,18.947 21.793,19.132C21.724,19.92 21.409,20.926 21.132,21.514C20.815,22.187 20.451,23.084 20.451,24.043C20.451,24.872 20.451,25.905 21.689,26.295C23.707,26.892 26.933,28 28.636,28.669C25.792,31.486 21.883,33.228 17.576,33.228C13.307,33.228 9.432,31.517 6.595,28.746Z"
android:fillColor="#A7A7A7"/>
</group>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="@color/white"/>
<stroke android:color="@color/orange"/>
<stroke android:width="2dp"/>
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="10dp"/>
<solid android:color="@color/white"/>
<stroke android:color="@color/orange"/>
<stroke android:width="4dp"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp"/>
<solid android:color="@color/white"/>
<stroke android:color="@color/grey"/>
<stroke android:width="1dp"/>
</shape>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="10dp"/>
</shape>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="@color/grey_light"/>
</shape>

View File

@ -0,0 +1,18 @@
<?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="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/entry_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,15 +2,149 @@
<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="match_parent">
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ProgressBar
android:id="@+id/loading"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="168dp"
android:text="@string/authorization"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintTop_toTopOf="parent">
</TextView>
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="300dp"
android:layout_height="180dp"
android:layout_marginTop="204dp"
android:outlineSpotShadowColor="@android:color/transparent"
app:cardBackgroundColor="@color/grey_light"
app:cardCornerRadius="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.cardview.widget.CardView
android:layout_width="244dp"
android:layout_height="50dp"
android:layout_marginStart="28dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="28dp"
android:outlineSpotShadowColor="@android:color/transparent"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="10dp">
<EditText
android:id="@+id/userLogin"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="13dp"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:hint="@string/login"
android:inputType="textEmailAddress"
android:maxLength="30"
android:maxLines="1"
android:textColorHint="@color/grey"
android:textSize="12sp">
</EditText>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="244dp"
android:layout_height="50dp"
android:layout_marginStart="28dp"
android:layout_marginTop="106dp"
android:layout_marginEnd="28dp"
android:outlineSpotShadowColor="@android:color/transparent"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="10dp">
<EditText
android:id="@+id/userPassword"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="13dp"
android:background="@android:color/transparent"
android:gravity="center_vertical"
android:hint="@string/password"
android:inputType="textPassword"
android:maxLength="20"
android:maxLines="1"
android:textColorHint="@color/grey"
android:textSize="12sp"
tools:visibility="visible">
</EditText>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="37dp"
android:layout_marginTop="160dp"
android:text="@string/password_hint"
android:textColor="@color/grey"
android:textSize="10sp">
</TextView>
</androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/sign_in_button"
android:layout_width="100dp"
android:layout_height="42dp"
android:layout_marginTop="66dp"
android:backgroundTint="@color/orange"
app:cornerRadius="10dp"
android:textSize="16sp"
app:circularflow_defaultRadius="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView"
app:circularflow_radiusInDP="1dp">
</com.google.android.material.button.MaterialButton>
<TextView
android:elevation="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="@string/sign_in"
android:textColor="@color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/sign_in_button"
app:layout_constraintEnd_toEndOf="@+id/sign_in_button"
app:layout_constraintStart_toStartOf="@+id/sign_in_button"
app:layout_constraintTop_toTopOf="@+id/sign_in_button">
</TextView>
<TextView
android:id="@+id/errorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Error"
android:textColor="#FF0000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView"
android:layout_marginTop="10dp">
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/entry_nav_graph"
app:startDestination="@id/login">
<fragment
tools:layout="@layout/fragment_login"
android:id="@+id/login"
android:name="ru.myitschool.work.ui.login.LoginFragment"
android:label="Authorization">
<action
android:id="@+id/action_auth_to_root"
app:destination="@id/rootActivity" />
</fragment>
<activity
android:id="@+id/rootActivity"
android:name="ru.myitschool.work.ui.RootActivity"
android:label="activity_root"
tools:layout="@layout/activity_root" />
</navigation>

View File

@ -7,4 +7,8 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#A7A7A7</color>
<color name="grey_light">#EBEBEB</color>
<color name="orange">#FF6900</color>
<color name="violet">#9F27FE</color>
</resources>

View File

@ -1,3 +1,11 @@
<resources>
<string name="app_name">NTO Pass</string>
<string name="authorization">Авторизация</string>
<string name="login">Логин</string>
<string name="password">Пароль</string>
<string name="password_hint">Пароль должен содержать не менее 8 символов</string>
<string name="sign_in">Войти</string>
<string name="error_valid">Ошибка валидации\n</string>
<string name="error_invalid_credentials">Неверный логин или пароль</string>
<string name="error_unknown">Непредвиденная ошибка</string>
</resources>

View File

@ -3,7 +3,7 @@
<style name="Theme.Default" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimaryVariant">@color/orange</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>