Compare commits

...

4 Commits

Author SHA1 Message Date
88ccddcdbb Add bookscreen#2 2025-12-05 18:59:45 +07:00
262573b71e Add bookscreen 2025-12-05 18:52:09 +07:00
b81bc48de5 Add save code 2025-12-05 15:40:51 +07:00
d0d2e1f849 Autorization screen 2025-12-03 19:09:08 +07:00
7 changed files with 264 additions and 23 deletions

View File

@@ -15,6 +15,7 @@ import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.BookScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.screen.auth.AuthScreen
import ru.myitschool.work.ui.screen.book.BookScreen
@Composable
fun AppNavHost(
@@ -35,15 +36,21 @@ fun AppNavHost(
Box(
contentAlignment = Alignment.Center
) {
Text(text = "Hello")
Text(text = "MainScreen")
}
}
composable<BookScreenDestination> {
Box(
contentAlignment = Alignment.Center
) {
Text(text = "Hello")
}
BookScreen(
selectedDateIndex = 0,
onDateClick = {},
//selectedPlaceIndex = null,
//onPlaceSelect = {},
error = null,
isEmpty = false,
onRefresh = {},
onBack = { navController.popBackStack() },
onBook = {}
)
}
}
}

View File

@@ -45,6 +45,9 @@ fun AuthScreen(
}
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -52,11 +55,7 @@ fun AuthScreen(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
when (val currentState = state) {
is AuthState.Data -> Content(viewModel, currentState)
is AuthState.Loading -> {
@@ -64,6 +63,7 @@ fun AuthScreen(
modifier = Modifier.size(64.dp)
)
}
is AuthState.Error -> Content(viewModel, currentState)
}
}
}
@@ -71,15 +71,23 @@ fun AuthScreen(
@Composable
private fun Content(
viewModel: AuthViewModel,
state: AuthState.Data
state: AuthState
) {
var inputText by remember { mutableStateOf("") }
val inputText = when (state) {
is AuthState.Data -> state.text
is AuthState.Error -> state.text
else -> "" }
Text(
text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(16.dp))
TextField(
modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(),
value = inputText,
onValueChange = {
inputText = it
viewModel.onIntent(AuthIntent.TextInput(it))
},
label = { Text(stringResource(R.string.auth_label)) }
@@ -89,9 +97,20 @@ private fun Content(
modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(),
onClick = {
viewModel.onIntent(AuthIntent.Send(inputText))
},
enabled = true
enabled = inputText.length == 4 && !inputText.isNullOrEmpty() && inputText.matches("[a-zA-Z0-9]+".toRegex()),
) {
Text(stringResource(R.string.auth_sign_in))
}
}
Spacer(modifier = Modifier.size(24.dp))
if (state is AuthState.Error) {
Text(
modifier = Modifier.testTag(TestIds.Auth.ERROR),
text = state.message,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
color = Color.Red
)
}
}

View File

@@ -2,5 +2,6 @@ package ru.myitschool.work.ui.screen.auth
sealed interface AuthState {
object Loading: AuthState
object Data: AuthState
class Data(val text: String = ""): AuthState
data class Error(val text: String, val message: String) : AuthState
}

View File

@@ -1,5 +1,10 @@
package ru.myitschool.work.ui.screen.auth
import android.app.Application
import android.content.Context
import android.content.Context.MODE_PRIVATE
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
@@ -13,31 +18,61 @@ import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
class AuthViewModel : ViewModel() {
class AuthViewModel(application: Application) : AndroidViewModel(application) {
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data)
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data())
val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
private val context = getApplication<Application>().applicationContext
private val prefs by lazy {
context.getSharedPreferences("auth", Context.MODE_PRIVATE)
}
init {
val savedCode = prefs.getString("saved_code", null)
if (!savedCode.isNullOrEmpty()) {
viewModelScope.launch {
_actionFlow.emit(Unit)
}
}
}
fun onIntent(intent: AuthIntent) {
when (intent) {
is AuthIntent.Send -> {
viewModelScope.launch(Dispatchers.Default) {
val currentText = when (val s = _uiState.value) {
is AuthState.Data -> s.text
is AuthState.Error -> s.text
else -> ""
}
_uiState.update { AuthState.Loading }
checkAndSaveAuthCodeUseCase.invoke("9999").fold(
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
onSuccess = {
prefs.edit()
.putString("saved_code", intent.text)
.apply()
_actionFlow.emit(Unit)
},
onFailure = { error ->
error.printStackTrace()
_actionFlow.emit(Unit)
_uiState.value = AuthState.Error(text = currentText,error.message ?: "Ошибка")
}
)
}
}
is AuthIntent.TextInput -> Unit
is AuthIntent.TextInput -> {
when (val stateValue = _uiState.value) {
is AuthState.Data -> _uiState.value = AuthState.Data(text = intent.text)
is AuthState.Error -> _uiState.value = AuthState.Data(intent.text)
is AuthState.Loading -> Unit
}
}
}
}
}

View File

@@ -0,0 +1,172 @@
package ru.myitschool.work.ui.screen.book
import android.R
import androidx.annotation.ColorRes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CheckboxDefaults.colors
import androidx.compose.material3.RadioButton
import androidx.compose.material3.RadioButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.screen.auth.AuthState
import ru.myitschool.work.ui.screen.auth.AuthViewModel
@Composable
fun BookScreen(
dates: List<String> = listOf("19.04", "20.04", "21.04", "22.04"),
selectedDateIndex: Int,
onDateClick: (Int) -> Unit,
places: List<String> = listOf(
"Рабочее место у окна",
"Переговорная комната №1",
"Коворкинг A"
),
myColor: Color = Color(0xFF0090FF),
error: String?,
isEmpty: Boolean,
onRefresh: () -> Unit,
onBack: () -> Unit,
onBook: () -> Unit
) {
var selectedPlaceIndex by remember { mutableStateOf<Int?>(null) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
onClick = onBack,
colors = ButtonDefaults.buttonColors( containerColor = myColor ),
)
{Text("Назад")}
Row(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.padding(top = 16.dp)
) {
dates.forEachIndexed { index, date ->
Box(
modifier = Modifier
.width(60.dp)
.height(40.dp)
.border(
width = 2.dp,
color = if (index == selectedDateIndex) myColor else Color.LightGray,
shape = RoundedCornerShape(6.dp)
)
.background(Color.White, RoundedCornerShape(6.dp))
.clickable { onDateClick(index) }
.padding(8.dp)
) {
Text(
text = date,
modifier = Modifier.align(Alignment.Center),
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.width(8.dp))
}
}
Spacer(Modifier.height(20.dp))
Column {
places.forEachIndexed { index, place ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Text(place)
RadioButton(
selected = selectedPlaceIndex == index,
onClick = { selectedPlaceIndex = index },
colors = RadioButtonDefaults.colors(
selectedColor = myColor,
unselectedColor = Color.Gray
)
)
}
}
}
if (error != null) {
Text(
text = error,
color = Color.Red,
modifier = Modifier.padding(top = 8.dp).testTag(TestIds.Book.ERROR)
)
}
if (isEmpty) {
Text(
text = "Всё забронировано",
modifier = Modifier.padding(top = 12.dp).testTag(TestIds.Book.EMPTY)
)
Button(
onClick = onRefresh,
modifier = Modifier.padding(top = 12.dp).testTag(TestIds.Book.REFRESH_BUTTON),
colors = ButtonDefaults.buttonColors( containerColor = myColor )
) {
Text("Обновить")
}
}
Button(
onClick = onBook,
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp),
colors = ButtonDefaults.buttonColors( containerColor = myColor )
) {
Text("Забронировать")
}
}
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.book;
import android.app.Application;
class BookViewModel {
}

View File

@@ -7,4 +7,5 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="primary_blue">#517DFF</color>
</resources>