checkpoint 0 #5

Open
student-e-klyukin wants to merge 15 commits from WindWin-org/NTO-2026-Android-TeamTask-Template:main into main
9 changed files with 115 additions and 24 deletions
Showing only changes of commit 311cab5b43 - Show all commits

View File

@ -1,10 +1,9 @@
package ru.myitschool.work.core package ru.myitschool.work.core
object Constants { object Constants {
const val HOST = "http://10.0.0.14:49182" // "http://10.0.0.14:49182" or "http://10.0.2.2:8080" const val HOST = "http://10.0.0.172:49182" // "http://10.0.0.14:49182" or "http://10.0.2.2:8080"
const val AUTH_URL = "/auth" const val AUTH_URL = "/auth"
const val INFO_URL = "/info" const val INFO_URL = "/info"
const val BOOKING_URL = "/booking" const val BOOKING_URL = "/booking"
const val BOOK_URL = "/book" const val BOOK_URL = "/book"
const val GET_TOKEN = "/getToken"
} }

View File

@ -1,6 +1,7 @@
package ru.myitschool.work.data.repo package ru.myitschool.work.data.repo
import android.content.Context import android.content.Context
import android.util.Log
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
@ -25,6 +26,10 @@ object AuthRepository {
} }
} }
} }
/**
* Из памяти
*/
suspend fun getToken(): String? { suspend fun getToken(): String? {
if (tokenCache == null) { if (tokenCache == null) {
tokenCache = App.context.userDataStore.data tokenCache = App.context.userDataStore.data
@ -33,10 +38,15 @@ object AuthRepository {
preferences[stringPreferencesKey(TOKEN_KEY)] preferences[stringPreferencesKey(TOKEN_KEY)]
} }
} }
Log.e("getTokenCache", tokenCache.toString())
return tokenCache return tokenCache
} }
/**
* При обращении к серверу
*/
suspend fun getToken(login : String, password : String) : Result<String> { suspend fun getToken(login : String, password : String) : Result<String> {
Log.e("getTokenNDSInAR", NetworkDataSource.getToken(login, password).toString())
return NetworkDataSource.getToken(login, password) return NetworkDataSource.getToken(login, password)
} }

View File

@ -42,22 +42,29 @@ object NetworkDataSource {
} }
suspend fun getToken(login: String, password: String): Result<String> = withContext(Dispatchers.IO) { suspend fun getToken(login: String, password: String): Result<String> = withContext(Dispatchers.IO) {
return@withContext runCatching { return@withContext runCatching {
val response = client.post("${Constants.HOST}/api${Constants.GET_TOKEN}") { val response = client.post("${Constants.HOST}/api${Constants.AUTH_URL}") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( Log.d("bodyInToken",
""" """ {
"login" : "$login", "login" : "$login",
"password" : "$password" "password" : "$password"
""".trimIndent() } """.trimIndent()
)
setBody(
"""{
"login" : "${login}",
"password" : "${password}"
} """.trimIndent()
) )
} }
Log.e("getTokenInNDS", response.body())
if (response.status != HttpStatusCode.OK) { if (response.status != HttpStatusCode.OK) {
error(response.status) Log.e("getToken", response.status.toString())
throw Exception("Неизвестная ошибка ${response.status}")
} }
else if (response.status == HttpStatusCode.Unauthorized) { else if (response.status == HttpStatusCode.Unauthorized) {
error("Неверный логин или пароль") throw Exception("Неверный логин или пароль")
} }
Log.e("getToken", response.status.toString())
response.body<String>() response.body<String>()
} }
} }

View File

@ -1,11 +1,13 @@
package ru.myitschool.work.domain.auth package ru.myitschool.work.domain.auth
import android.util.Log
import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.data.repo.AuthRepository
class GetTokenNetworkUseCase( class GetTokenNetworkUseCase(
private val repository: AuthRepository private val repository: AuthRepository
) { ) {
suspend operator fun invoke(login : String, password: String): Result<String> { suspend operator fun invoke(login : String, password: String): Result<String> {
Log.e("GetTokenNetworkUseCase", repository.getToken(login, password).toString())
return repository.getToken(login, password) return repository.getToken(login, password)
} }
} }

View File

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecureTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -25,10 +26,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import io.ktor.http.HttpStatusCode
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
import ru.myitschool.work.ui.custom.component.CustomButon import ru.myitschool.work.ui.custom.component.CustomButon
@ -71,10 +74,10 @@ fun AuthScreen(
} }
} }
} }
@Composable //@Composable
fun SecureScreen(enabled: Boolean = true) { //fun SecureScreen(enabled: Boolean = true) {
//
} //}
@Composable @Composable
private fun Content( private fun Content(
viewModel: AuthViewModel, viewModel: AuthViewModel,
@ -112,14 +115,30 @@ private fun Content(
) { ) {
Text(stringResource(R.string.auth_sign_in)) Text(stringResource(R.string.auth_sign_in))
} }
if (state.error != "Connection refused" && state.error != null) { // if (state.error != null) {
// Text(
// modifier = Modifier.testTag(TestIds.Auth.ERROR),
// text = state.error,
// style = MaterialTheme.typography.bodyMedium,
// color = Color.Red,
// )
// }
if (state.error != null && !state.error.contains("401") && state.error.contains("Network")) {
Text( Text(
modifier = Modifier.testTag(TestIds.Auth.ERROR), modifier = Modifier.testTag(TestIds.Auth.ERROR),
text = "Неизвестная ошибка: ${state.error}", text = state.error,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = Color.Red, color = Color.Red,
) )
} else if (state.error == "Connection refused") { }
if (state.error.toString().contains("401")) {
Text(
modifier = Modifier.testTag(TestIds.Auth.ERROR),
text = "Неверный логин или пароль",
style = MaterialTheme.typography.bodyMedium,
color = Color.Red,
)
} else if (state.error.toString().contains("Network")) {
Text( Text(
modifier = Modifier.testTag(TestIds.Auth.ERROR), modifier = Modifier.testTag(TestIds.Auth.ERROR),
text = "Отсутствует интернет-соединение", text = "Отсутствует интернет-соединение",

View File

@ -30,10 +30,10 @@ class AuthViewModel : ViewModel() {
private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow() private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow()
val actionFlow: SharedFlow<AuthAction> = _actionFlow val actionFlow: SharedFlow<AuthAction> = _actionFlow
fun onIntent(intent: AuthIntent) { fun onIntent(intent: AuthIntent) {
when (intent) { when (intent) {
is AuthIntent.Send -> { is AuthIntent.Send -> {
Log.e("onIntent", intent.login)
viewModelScope.launch { viewModelScope.launch {
getTokenNetworkUseCase.invoke(intent.login, intent.password).fold( getTokenNetworkUseCase.invoke(intent.login, intent.password).fold(
onSuccess = { onSuccess = {

View File

@ -1,4 +1,4 @@
package ru.myitschool.work.ui.screen.main package ru.myitschool.work.ui.screen.meetings
object MeetingResult { object MeetingResult {
const val REFRESH_STATUS = "refresh_status" const val REFRESH_STATUS = "refresh_status"

View File

@ -1,4 +1,10 @@
package ru.myitschool.work.ui.screen.meetings package ru.myitschool.work.ui.screen.meetings
class MeetingsAction { import ru.myitschool.work.ui.nav.AppDestination
sealed interface MeetingsAction {
class Open(
val destination: AppDestination,
val clearBackStack: Boolean = false
): MeetingsAction
} }

View File

@ -1,18 +1,66 @@
package ru.myitschool.work.ui.screen.meetings package ru.myitschool.work.ui.screen.meetings
import android.provider.Settings.System.DATE_FORMAT
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.domain.auth.LogoutUseCase
import ru.myitschool.work.domain.main.GetMainDataUseCase
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.screen.main.MainAction
import ru.myitschool.work.ui.screen.main.MainState
import java.time.LocalDate
import java.time.format.DateTimeFormatter
class MeetingsViewModel : ViewModel() { class MeetingsViewModel: ViewModel() {
private val _uiState = MutableStateFlow<MeetingsState>(MeetingsState.Loading) private val _uiState = MutableStateFlow<MeetingsState>(MeetingsState.Loading)
val uiState: StateFlow<MeetingsState> = _uiState.asStateFlow() val uiState: StateFlow<MeetingsState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<MainAction> = MutableSharedFlow()
val actionFlow: SharedFlow<MainAction> = _actionFlow
private val logoutUseCase by lazy {
LogoutUseCase(AuthRepository)
}
private val getMeetingsDataUseCase by lazy {
GetMainDataUseCase(BookRepository(AuthRepository))
}
init {
private val _actionFlow: MutableSharedFlow<MeetingsAction> = MutableSharedFlow() }
val actionFlow: SharedFlow<MeetingsAction> = _actionFlow // fun onIntent(intent: MeetingIntent) {
// when(intent) {
// is MeetingIntent.Logout -> {
// viewModelScope.launch {
// logoutUseCase.invoke()
// _actionFlow.emit(/*TODo*/)
// }
// }
// is MeetingIntent.Refresh -> {
// /*TODO("обновляться автоматически с заданной периодичностью")*/
// }
// }
// }
fun refresh() {
viewModelScope.launch {
_uiState.update { MeetingsState.Loading }
_uiState.update {
getMeetingsDataUseCase.invoke().fold(
onFailure = { error ->
MeetingsState.Error(
error = error.message?.takeIf { it.isNotBlank() } ?: "Unknown error"
)
},
onSuccess = TODO()
)
}
}
}
} }