diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a28d464..1421b3f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a986978..8f78226 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,15 +19,23 @@
android:supportsRtl="true"
android:theme="@style/Theme.Default"
tools:targetApi="31">
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/data/auth/AuthNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/auth/AuthNetworkDataSource.kt
new file mode 100644
index 0000000..b8b7bc7
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/data/auth/AuthNetworkDataSource.kt
@@ -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 = 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 = 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()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/data/auth/AuthRepoImpl.kt b/app/src/main/java/ru/myitschool/work/data/auth/AuthRepoImpl.kt
new file mode 100644
index 0000000..b5b6952
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/data/auth/AuthRepoImpl.kt
@@ -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 {
+ return authNetworkDataSource.isUserExist(email)
+ }
+
+ override suspend fun login(email: String, password: String): Result {
+ val token = authStorageDataSource.updateToken(email, password)
+
+ val userInfo = authNetworkDataSource.login(token).onFailure {
+ authStorageDataSource.clear()
+ }
+ if (userInfo.isSuccess){
+ authStorageDataSource.updateUserInfo(userInfo)
+ }
+
+ return userInfo
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/data/auth/AuthStorageDataSource.kt b/app/src/main/java/ru/myitschool/work/data/auth/AuthStorageDataSource.kt
new file mode 100644
index 0000000..38f2a18
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/data/auth/AuthStorageDataSource.kt
@@ -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.onSuccess { user ->
+ userInfo = user
+ }.onFailure { error ->
+ userInfo = null
+ error("Server Error id = null")
+ }
+
+ }
+ fun clear() {
+ token = null
+ userInfo = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/data/auth/Network.kt b/app/src/main/java/ru/myitschool/work/data/auth/Network.kt
new file mode 100644
index 0000000..86712f4
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/data/auth/Network.kt
@@ -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
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/data/user/UserDto.kt b/app/src/main/java/ru/myitschool/work/data/user/UserDto.kt
new file mode 100644
index 0000000..a591b96
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/data/user/UserDto.kt
@@ -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
+ )
+ }
+}
diff --git a/app/src/main/java/ru/myitschool/work/domain/user/UserEntity.kt b/app/src/main/java/ru/myitschool/work/domain/user/UserEntity.kt
new file mode 100644
index 0000000..34f7609
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/domain/user/UserEntity.kt
@@ -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
+
+)
diff --git a/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt b/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
index 88a796a..4cfeab2 100644
--- a/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
+++ b/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
@@ -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()
+ }*/
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/ui/login/EntryActivity.kt b/app/src/main/java/ru/myitschool/work/ui/login/EntryActivity.kt
new file mode 100644
index 0000000..9960af8
--- /dev/null
+++ b/app/src/main/java/ru/myitschool/work/ui/login/EntryActivity.kt
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt
index 02842ce..725834c 100644
--- a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt
+++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt
@@ -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()
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt
index 3a53d6c..c7edf14 100644
--- a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt
+++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt
@@ -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()
+ val navigateToMain = _navigateToMain.asSharedFlow()
+
+ private val _userRole = MutableSharedFlow()
+ 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 create(modelClass: KClass, 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
+ }
+
+ }
+ }*/
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml
new file mode 100644
index 0000000..e4f06c7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logo.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_profile.xml b/app/src/main/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..06de96f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_profile.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_button_additional.xml b/app/src/main/res/drawable/shape_button_additional.xml
new file mode 100644
index 0000000..1b2d2a9
--- /dev/null
+++ b/app/src/main/res/drawable/shape_button_additional.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_button_second.xml b/app/src/main/res/drawable/shape_button_second.xml
new file mode 100644
index 0000000..1956084
--- /dev/null
+++ b/app/src/main/res/drawable/shape_button_second.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_edit_profile.xml b/app/src/main/res/drawable/shape_edit_profile.xml
new file mode 100644
index 0000000..8baca05
--- /dev/null
+++ b/app/src/main/res/drawable/shape_edit_profile.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_edit_text_login.xml b/app/src/main/res/drawable/shape_edit_text_login.xml
new file mode 100644
index 0000000..a59e2fe
--- /dev/null
+++ b/app/src/main/res/drawable/shape_edit_text_login.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_logout_button.xml b/app/src/main/res/drawable/shape_logout_button.xml
new file mode 100644
index 0000000..f18ea8d
--- /dev/null
+++ b/app/src/main/res/drawable/shape_logout_button.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_rounded.xml b/app/src/main/res/drawable/shape_rounded.xml
new file mode 100644
index 0000000..6cfef26
--- /dev/null
+++ b/app/src/main/res/drawable/shape_rounded.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_entry.xml b/app/src/main/res/layout/activity_entry.xml
new file mode 100644
index 0000000..747b55d
--- /dev/null
+++ b/app/src/main/res/layout/activity_entry.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml
index 7f3cd66..cd83595 100644
--- a/app/src/main/res/layout/fragment_login.xml
+++ b/app/src/main/res/layout/fragment_login.xml
@@ -2,15 +2,149 @@
+ android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools">
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/entry_nav_graph.xml b/app/src/main/res/navigation/entry_nav_graph.xml
new file mode 100644
index 0000000..926b34c
--- /dev/null
+++ b/app/src/main/res/navigation/entry_nav_graph.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..3d1a6ab 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,8 @@
#FF018786
#FF000000
#FFFFFFFF
+ #A7A7A7
+ #EBEBEB
+ #FF6900
+ #9F27FE
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b183019..cde5bd7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,11 @@
NTO Pass
+ Авторизация
+ Логин
+ Пароль
+ Пароль должен содержать не менее 8 символов
+ Войти
+ Ошибка валидации\n
+ Неверный логин или пароль
+ Непредвиденная ошибка
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 89e63d4..b93b2f2 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -3,7 +3,7 @@