From 2d44a17c2c4e4a5f614a77d1caf923d36f28b256 Mon Sep 17 00:00:00 2001 From: DKaverznev Date: Tue, 18 Feb 2025 20:36:53 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D1=87=D1=82=D0=B8=20=D1=81=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 +- .../ru/myitschool/work/api/NetworkModule.kt | 9 ++ .../ru/myitschool/work/api/RetrofitClient.kt | 10 ++ .../work/api/{ => login}/ApiServiceLogin.kt | 2 +- .../work/api/{ => main}/ApiServiceMain.kt | 2 +- .../work/api/{ => main}/UserInfo.kt | 2 +- .../work/api/scan/ApiServiceScan.kt | 11 +++ .../ru/myitschool/work/api/scan/CodeJson.kt | 5 + .../java/ru/myitschool/work/core/Constants.kt | 2 +- .../ru/myitschool/work/ui/RootActivity.kt | 4 +- .../work/ui/login/LoginViewModel.kt | 4 +- .../myitschool/work/ui/main/MainFragment.kt | 1 - .../myitschool/work/ui/main/MainViewModel.kt | 4 +- .../work/ui/qr/scan/QrScanDestination.kt | 1 - .../work/ui/qr/scan/QrScanFragment.kt | 1 - .../work/ui/qr/scan/QrScanViewModel.kt | 1 - .../work/ui/result/LoginViewModel.kt | 63 ++++++++++++ .../work/ui/result/ResultDestination.kt | 6 ++ .../work/ui/result/ResultFragment.kt | 99 +++++++++++++++++++ .../myitschool/work/ui/result/TextStatus.kt | 6 ++ .../myitschool/work/utils/AuthPreferences.kt | 2 +- .../main/res/layout/fragment_scan_result.xml | 29 +++++- app/src/main/res/values/strings.xml | 2 + 23 files changed, 250 insertions(+), 18 deletions(-) rename app/src/main/java/ru/myitschool/work/api/{ => login}/ApiServiceLogin.kt (84%) rename app/src/main/java/ru/myitschool/work/api/{ => main}/ApiServiceMain.kt (84%) rename app/src/main/java/ru/myitschool/work/api/{ => main}/UserInfo.kt (94%) create mode 100644 app/src/main/java/ru/myitschool/work/api/scan/ApiServiceScan.kt create mode 100644 app/src/main/java/ru/myitschool/work/api/scan/CodeJson.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/result/LoginViewModel.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/result/ResultDestination.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/result/ResultFragment.kt create mode 100644 app/src/main/java/ru/myitschool/work/ui/result/TextStatus.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a28d464..51c1ecc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { 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("androidx.datastore:datastore-preferences:1.1.2") implementation("com.google.mlkit:barcode-scanning:17.3.0") val cameraX = "1.3.4" diff --git a/app/src/main/java/ru/myitschool/work/api/NetworkModule.kt b/app/src/main/java/ru/myitschool/work/api/NetworkModule.kt index 4be78d5..9d6ad25 100644 --- a/app/src/main/java/ru/myitschool/work/api/NetworkModule.kt +++ b/app/src/main/java/ru/myitschool/work/api/NetworkModule.kt @@ -4,6 +4,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import ru.myitschool.work.api.login.ApiServiceLogin +import ru.myitschool.work.api.main.ApiServiceMain +import ru.myitschool.work.api.scan.ApiServiceScan import javax.inject.Singleton @Module @@ -27,4 +30,10 @@ object NetworkModule { fun provideApiServiceMain(retrofitClient: RetrofitClient): ApiServiceMain { return retrofitClient.getApiServiceMain() } + + @Provides + @Singleton + fun provideApiServiceScan(retrofitClient: RetrofitClient): ApiServiceScan { + return retrofitClient.getApiServiceScan() + } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/api/RetrofitClient.kt b/app/src/main/java/ru/myitschool/work/api/RetrofitClient.kt index 88d17ae..81b4bbd 100644 --- a/app/src/main/java/ru/myitschool/work/api/RetrofitClient.kt +++ b/app/src/main/java/ru/myitschool/work/api/RetrofitClient.kt @@ -1,11 +1,17 @@ package ru.myitschool.work.api +import com.google.gson.Gson +import com.google.gson.GsonBuilder import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import ru.myitschool.work.api.login.ApiServiceLogin +import ru.myitschool.work.api.main.ApiServiceMain +import ru.myitschool.work.api.scan.ApiServiceScan import ru.myitschool.work.core.Constants.SERVER_ADDRESS import java.util.concurrent.TimeUnit + class RetrofitClient { private val serverAddress = SERVER_ADDRESS @@ -30,4 +36,8 @@ class RetrofitClient { fun getApiServiceMain(): ApiServiceMain { return retrofit.create(ApiServiceMain::class.java) } + + fun getApiServiceScan(): ApiServiceScan { + return retrofit.create(ApiServiceScan::class.java) + } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/api/ApiServiceLogin.kt b/app/src/main/java/ru/myitschool/work/api/login/ApiServiceLogin.kt similarity index 84% rename from app/src/main/java/ru/myitschool/work/api/ApiServiceLogin.kt rename to app/src/main/java/ru/myitschool/work/api/login/ApiServiceLogin.kt index ffc5f0b..ed7b501 100644 --- a/app/src/main/java/ru/myitschool/work/api/ApiServiceLogin.kt +++ b/app/src/main/java/ru/myitschool/work/api/login/ApiServiceLogin.kt @@ -1,4 +1,4 @@ -package ru.myitschool.work.api +package ru.myitschool.work.api.login import retrofit2.Call import retrofit2.http.GET diff --git a/app/src/main/java/ru/myitschool/work/api/ApiServiceMain.kt b/app/src/main/java/ru/myitschool/work/api/main/ApiServiceMain.kt similarity index 84% rename from app/src/main/java/ru/myitschool/work/api/ApiServiceMain.kt rename to app/src/main/java/ru/myitschool/work/api/main/ApiServiceMain.kt index 560a5e7..496b32e 100644 --- a/app/src/main/java/ru/myitschool/work/api/ApiServiceMain.kt +++ b/app/src/main/java/ru/myitschool/work/api/main/ApiServiceMain.kt @@ -1,4 +1,4 @@ -package ru.myitschool.work.api +package ru.myitschool.work.api.main import retrofit2.Call import retrofit2.http.GET diff --git a/app/src/main/java/ru/myitschool/work/api/UserInfo.kt b/app/src/main/java/ru/myitschool/work/api/main/UserInfo.kt similarity index 94% rename from app/src/main/java/ru/myitschool/work/api/UserInfo.kt rename to app/src/main/java/ru/myitschool/work/api/main/UserInfo.kt index 40babdb..31fc8d1 100644 --- a/app/src/main/java/ru/myitschool/work/api/UserInfo.kt +++ b/app/src/main/java/ru/myitschool/work/api/main/UserInfo.kt @@ -1,4 +1,4 @@ -package ru.myitschool.work.api +package ru.myitschool.work.api.main import com.google.gson.annotations.SerializedName diff --git a/app/src/main/java/ru/myitschool/work/api/scan/ApiServiceScan.kt b/app/src/main/java/ru/myitschool/work/api/scan/ApiServiceScan.kt new file mode 100644 index 0000000..093cd5f --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/api/scan/ApiServiceScan.kt @@ -0,0 +1,11 @@ +package ru.myitschool.work.api.scan + +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.PATCH +import retrofit2.http.Path + +interface ApiServiceScan { + @PATCH("api/{login}/info") + fun open(@Path("login") login: String, @Body data: CodeJson): Call +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/api/scan/CodeJson.kt b/app/src/main/java/ru/myitschool/work/api/scan/CodeJson.kt new file mode 100644 index 0000000..44615b1 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/api/scan/CodeJson.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.api.scan + +class CodeJson( + private var value: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/core/Constants.kt b/app/src/main/java/ru/myitschool/work/core/Constants.kt index 971f65a..8f8138d 100644 --- a/app/src/main/java/ru/myitschool/work/core/Constants.kt +++ b/app/src/main/java/ru/myitschool/work/core/Constants.kt @@ -1,5 +1,5 @@ package ru.myitschool.work.core -// БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ + object Constants { const val SERVER_ADDRESS = "http://localhost:8090" } \ No newline at end of file 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 eb57870..b0de275 100644 --- a/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt +++ b/app/src/main/java/ru/myitschool/work/ui/RootActivity.kt @@ -15,8 +15,9 @@ import ru.myitschool.work.ui.main.MainDestination import ru.myitschool.work.ui.main.MainFragment import ru.myitschool.work.ui.qr.scan.QrScanDestination import ru.myitschool.work.ui.qr.scan.QrScanFragment +import ru.myitschool.work.ui.result.ResultDestination +import ru.myitschool.work.ui.result.ResultFragment -// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА! @AndroidEntryPoint class RootActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -34,6 +35,7 @@ class RootActivity : AppCompatActivity() { fragment() fragment() fragment() + fragment() } } 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 b17c930..f8f94d0 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 @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import retrofit2.Call import retrofit2.Response -import ru.myitschool.work.api.ApiServiceLogin +import ru.myitschool.work.api.login.ApiServiceLogin import javax.inject.Inject @HiltViewModel @@ -22,7 +22,7 @@ class LoginViewModel @Inject constructor( fun authenticate(login: String) { viewModelScope.launch { - _state.value = LoginState.Loading // Устанавливаем состояние загрузки + _state.value = LoginState.Loading apiService.authenticate(login).enqueue(object : retrofit2.Callback { override fun onResponse(call: Call, response: Response) { diff --git a/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt index cf30fea..f01e856 100644 --- a/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/main/MainFragment.kt @@ -82,7 +82,6 @@ class MainFragment : Fragment(R.layout.main_fragment) { username.text = state.userInfo.name position.text = state.userInfo.position - // Загрузка изображения из URL loadImageFromUrl(state.userInfo.photoUrl) val inputFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()) diff --git a/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt index 85f595a..f28cf79 100644 --- a/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/main/MainViewModel.kt @@ -10,8 +10,8 @@ import kotlinx.coroutines.launch import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import ru.myitschool.work.api.ApiServiceMain -import ru.myitschool.work.api.UserInfo +import ru.myitschool.work.api.main.ApiServiceMain +import ru.myitschool.work.api.main.UserInfo import javax.inject.Inject @HiltViewModel diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt index 7e34b28..d7b8e6d 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanDestination.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.core.os.bundleOf import kotlinx.serialization.Serializable -// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ @Serializable data object QrScanDestination { const val REQUEST_KEY = "qr_result" diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt index a9ddaab..38beb4d 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt @@ -23,7 +23,6 @@ import ru.myitschool.work.databinding.FragmentQrScanBinding import ru.myitschool.work.utils.collectWhenStarted import ru.myitschool.work.utils.visibleOrGone -// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ class QrScanFragment : Fragment(R.layout.fragment_qr_scan) { private var _binding: FragmentQrScanBinding? = null private val binding: FragmentQrScanBinding get() = _binding!! diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt index 14565ab..e4fd4da 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import ru.myitschool.work.utils.MutablePublishFlow -// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ class QrScanViewModel( application: Application ) : AndroidViewModel(application) { diff --git a/app/src/main/java/ru/myitschool/work/ui/result/LoginViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/result/LoginViewModel.kt new file mode 100644 index 0000000..234defd --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/result/LoginViewModel.kt @@ -0,0 +1,63 @@ +package ru.myitschool.work.ui.result + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import retrofit2.Call +import retrofit2.Response +import ru.myitschool.work.api.scan.ApiServiceScan +import ru.myitschool.work.api.scan.CodeJson +import javax.inject.Inject + +@HiltViewModel +class ResultViewModel @Inject constructor( + private val apiService: ApiServiceScan +) : ViewModel() { + + private val _state = MutableStateFlow(ResultState.Initial) + val state = _state.asStateFlow() + + fun open(login: String, data: CodeJson) { + viewModelScope.launch { + _state.value = ResultState.Loading + + apiService.open(login, data).enqueue(object : retrofit2.Callback { + override fun onResponse(call: Call, response: Response) { + Log.d("Open", "Response code: ${response.code()}") + + when (response.code()) { + 200 -> { + _state.value = ResultState.Success + } + 500 -> { + _state.value = ResultState.InvalidCredentials + } + 401 -> { + _state.value = ResultState.Error + } + else -> { + _state.value = ResultState.InvalidCredentials + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + _state.value = ResultState.Error + } + }) + } + } + + + sealed class ResultState { + data object Initial : ResultState() + data object InvalidCredentials : ResultState() + data object Loading : ResultState() + data object Success : ResultState() + data object Error : ResultState() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/result/ResultDestination.kt b/app/src/main/java/ru/myitschool/work/ui/result/ResultDestination.kt new file mode 100644 index 0000000..6181f1f --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/result/ResultDestination.kt @@ -0,0 +1,6 @@ +package ru.myitschool.work.ui.result + +import kotlinx.serialization.Serializable + +@Serializable +data object ResultDestination \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/result/ResultFragment.kt b/app/src/main/java/ru/myitschool/work/ui/result/ResultFragment.kt new file mode 100644 index 0000000..6b77e50 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/result/ResultFragment.kt @@ -0,0 +1,99 @@ +package ru.myitschool.work.ui.result + +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import ru.myitschool.work.R +import androidx.navigation.fragment.findNavController +import androidx.transition.Visibility +import ru.myitschool.work.databinding.FragmentScanResultBinding +import ru.myitschool.work.ui.login.LoginViewModel +import ru.myitschool.work.ui.main.MainDestination +import ru.myitschool.work.utils.AuthPreferences +import ru.myitschool.work.utils.collectWhenStarted +import ru.myitschool.work.utils.visibleOrGone + +@AndroidEntryPoint +class ResultFragment : Fragment(R.layout.fragment_scan_result) { + private var _binding: FragmentScanResultBinding? = null + private val binding: FragmentScanResultBinding get() = _binding!! + private val viewModel: ResultViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentScanResultBinding.bind(view) + + setupLoginComponents() + } + + private fun setupLoginComponents() { + binding.apply { + result.visibleOrGone(false) + successIcon.visibleOrGone(false) + } + } + + private fun observeLoginState() { + viewModel.state.collectWhenStarted(this) { state -> + when (state) { + is ResultViewModel.ResultState.Loading -> { + binding.result.visibleOrGone(false) + binding.successIcon.visibleOrGone(false) + binding.error.visibleOrGone(false) + + binding.loading.visibleOrGone(true) + } + is ResultViewModel.ResultState.Success -> { + binding.result.visibleOrGone(true) + binding.successIcon.visibleOrGone(true) + + binding.error.visibleOrGone(false) + binding.loading.visibleOrGone(false) + } + is ResultViewModel.ResultState.InvalidCredentials -> { + binding.result.visibleOrGone(false) + binding.successIcon.visibleOrGone(false) + binding.loading.visibleOrGone(false) + + binding.error.visibleOrGone(true) + } + is ResultViewModel.ResultState.Error -> { + binding.loading.visibleOrGone(false) + binding.result.apply { + visibleOrGone(true) + text = RiootoString(R.string.userNotFing) + } + Log.d("Authentication", "Ошибка сканирования") + } + + LoginViewModel.LoginState.Initial -> binding.loading.visibleOrGone(false) + } + } + } + + private fun navigateToMainScreen() { + try { + findNavController().apply { + popBackStack(MainDestination, false) + navigate(MainDestination) + } + } catch (e: Exception) { + Log.e("ResultFragment", "Navigation error", e) + Toast.makeText(context, "Ошибка перехода", Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/result/TextStatus.kt b/app/src/main/java/ru/myitschool/work/ui/result/TextStatus.kt new file mode 100644 index 0000000..cb4a621 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/result/TextStatus.kt @@ -0,0 +1,6 @@ +package ru.myitschool.work.ui.result + +object TextStatus { + const val error = "Вход был отменён/Operation was cancelled" + const val success = "Успешно/Success" +} diff --git a/app/src/main/java/ru/myitschool/work/utils/AuthPreferences.kt b/app/src/main/java/ru/myitschool/work/utils/AuthPreferences.kt index cfd04b0..7bee323 100644 --- a/app/src/main/java/ru/myitschool/work/utils/AuthPreferences.kt +++ b/app/src/main/java/ru/myitschool/work/utils/AuthPreferences.kt @@ -12,7 +12,7 @@ class AuthPreferences(context: Context) { fun saveLoginState(isLoggedIn: Boolean) { sharedPreferences.edit().apply { putBoolean(KEY_IS_LOGGED_IN, isLoggedIn) - apply() // Асинхронное сохранение + apply() } } diff --git a/app/src/main/res/layout/fragment_scan_result.xml b/app/src/main/res/layout/fragment_scan_result.xml index 59b0e9e..7df2e90 100644 --- a/app/src/main/res/layout/fragment_scan_result.xml +++ b/app/src/main/res/layout/fragment_scan_result.xml @@ -5,6 +5,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + app:layout_constraintGuide_percent="0.7" /> + \ 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 09c6b87..e998535 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,4 +19,6 @@ Закрыть/Close Ошибка входа Последний вход 12.12.1212 + Ошибка сервера + Пользователь не найден \ No newline at end of file