forked from Olympic/NTO-2025-Android-TeamTask
Compare commits
2 Commits
487d228a9b
...
48f4f9fd6f
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f4f9fd6f | |||
| ca1850b97a |
@@ -36,7 +36,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
defaultComposeLibrary()
|
defaultComposeLibrary()
|
||||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
implementation("androidx.datastore:datastore-preferences:1.2.0")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
|
||||||
implementation("androidx.navigation:navigation-compose:2.9.6")
|
implementation("androidx.navigation:navigation-compose:2.9.6")
|
||||||
val coil = "3.3.0"
|
val coil = "3.3.0"
|
||||||
|
|||||||
@@ -2,11 +2,21 @@ package ru.myitschool.work
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import ru.myitschool.work.data.datastore.DataStoreManager
|
||||||
|
|
||||||
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "datastore")
|
||||||
|
|
||||||
class App: Application() {
|
class App: Application() {
|
||||||
|
|
||||||
|
lateinit var dataStoreManager: DataStoreManager
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
context = this
|
context = this
|
||||||
|
dataStoreManager = DataStoreManager(dataStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package ru.myitschool.work.data.datastore
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class DataStoreManager(
|
||||||
|
private val dataStore: DataStore<Preferences>
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val USER_CODE_KEY = stringPreferencesKey("user_code")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveUserCode(userCode: UserCode) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[USER_CODE_KEY] = userCode.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserCode(): Flow<UserCode> = dataStore.data.map { preferences ->
|
||||||
|
UserCode(
|
||||||
|
code = preferences[USER_CODE_KEY] ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package ru.myitschool.work.data.datastore
|
||||||
|
|
||||||
|
data class UserCode(
|
||||||
|
val code: String
|
||||||
|
)
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package ru.myitschool.work.ui
|
package ru.myitschool.work.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -17,6 +15,10 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -131,13 +133,13 @@ fun BaseText14(
|
|||||||
fun BaseInputText(
|
fun BaseInputText(
|
||||||
placeholder: String= "",
|
placeholder: String= "",
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
valueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
value: String
|
value: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = valueChange,
|
onValueChange = onValueChange,
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
BaseText16(
|
BaseText16(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.myitschool.work.ui.nav
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object SplashScreenDestination: AppDestination
|
||||||
@@ -14,9 +14,10 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||||
|
import ru.myitschool.work.ui.nav.SplashScreenDestination
|
||||||
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
||||||
import ru.myitschool.work.ui.screen.book.BookScreen
|
|
||||||
import ru.myitschool.work.ui.screen.main.MainScreen
|
import ru.myitschool.work.ui.screen.main.MainScreen
|
||||||
|
import ru.myitschool.work.ui.screen.splash.SplashScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavHost(
|
fun AppNavHost(
|
||||||
@@ -28,21 +29,23 @@ fun AppNavHost(
|
|||||||
enterTransition = { EnterTransition.None },
|
enterTransition = { EnterTransition.None },
|
||||||
exitTransition = { ExitTransition.None },
|
exitTransition = { ExitTransition.None },
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = AuthScreenDestination,
|
startDestination = SplashScreenDestination,
|
||||||
) {
|
) {
|
||||||
|
composable<SplashScreenDestination> {
|
||||||
|
SplashScreen(navController = navController)
|
||||||
|
}
|
||||||
composable<AuthScreenDestination> {
|
composable<AuthScreenDestination> {
|
||||||
AuthScreen(
|
AuthScreen(navController = navController)
|
||||||
navController = navController)
|
|
||||||
}
|
}
|
||||||
composable<MainScreenDestination> {
|
composable<MainScreenDestination> {
|
||||||
MainScreen(
|
MainScreen(navController = navController)
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
composable<BookScreenDestination> {
|
composable<BookScreenDestination> {
|
||||||
BookScreen(
|
Box(
|
||||||
navController = navController
|
contentAlignment = Alignment.Center
|
||||||
)
|
) {
|
||||||
|
Text(text = "Hello")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,24 @@
|
|||||||
package ru.myitschool.work.ui.screen.auth
|
package ru.myitschool.work.ui.screen.auth
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import ru.myitschool.work.R
|
import ru.myitschool.work.R
|
||||||
@@ -46,130 +36,95 @@ import ru.myitschool.work.ui.theme.White
|
|||||||
@Composable
|
@Composable
|
||||||
fun AuthScreen(
|
fun AuthScreen(
|
||||||
viewModel: AuthViewModel = viewModel(),
|
viewModel: AuthViewModel = viewModel(),
|
||||||
navController: NavController
|
navController: NavController,
|
||||||
) {
|
) {
|
||||||
Column(
|
val state by viewModel.uiState.collectAsState()
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
LaunchedEffect(Unit) {
|
||||||
.width(200.dp)
|
viewModel.actionFlow.collect {
|
||||||
.padding(20.dp),
|
navController.navigate(MainScreenDestination)
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Logo()
|
|
||||||
|
|
||||||
BaseText24(
|
|
||||||
text = stringResource(R.string.auth_title),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 20.dp)
|
modifier = Modifier
|
||||||
|
.width(400.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(20.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val textState by viewModel.textState.collectAsStateWithLifecycle()
|
Logo()
|
||||||
val errorState by viewModel.errorState.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
BaseInputText(
|
BaseText24(
|
||||||
value = textState,
|
text = stringResource(R.string.auth_title),
|
||||||
placeholder = stringResource(R.string.auth_label),
|
textAlign = TextAlign.Center
|
||||||
modifier = Modifier
|
|
||||||
.testTag(TestIds.Auth.CODE_INPUT)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
valueChange = {viewModel.updateText(it)}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (errorState) {
|
when (state) {
|
||||||
BaseText12(
|
is AuthState.Data -> Content(viewModel)
|
||||||
text = "Недействительный код сотрудника",
|
is AuthState.Loading -> {
|
||||||
color = Red,
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.testTag(TestIds.Auth.ERROR)
|
.padding(top = 40.dp)
|
||||||
.padding(
|
.size(64.dp)
|
||||||
start = 10.dp,
|
)
|
||||||
top = 5.dp,
|
}
|
||||||
bottom = 0.dp
|
|
||||||
)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isButtonEnabled by viewModel.isButtonEnabled.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
BaseButton(
|
|
||||||
text = stringResource(R.string.auth_sign_in),
|
|
||||||
onClick = { navController.navigate(MainScreenDestination)},
|
|
||||||
btnColor = Blue,
|
|
||||||
enable = isButtonEnabled,
|
|
||||||
btnContentColor = White,
|
|
||||||
modifier = Modifier
|
|
||||||
.testTag(TestIds.Auth.SIGN_BUTTON)
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Content(
|
||||||
|
viewModel: AuthViewModel
|
||||||
|
) {
|
||||||
|
|
||||||
//@Composable
|
val isButtonEnabled by viewModel.isButtonEnabled.collectAsState()
|
||||||
//fun AuthScreen(
|
val errorStateValue by viewModel.errorStateValue.collectAsState()
|
||||||
// viewModel: AuthViewModel = viewModel(),
|
val textState by viewModel.textState.collectAsState()
|
||||||
// navController: NavController
|
|
||||||
//) {
|
Column(
|
||||||
// val state by viewModel.uiState.collectAsState()
|
modifier = Modifier.padding(vertical = 20.dp)
|
||||||
//
|
) {
|
||||||
// LaunchedEffect(Unit) {
|
|
||||||
// viewModel.actionFlow.collect {
|
BaseInputText(
|
||||||
// navController.navigate(MainScreenDestination)
|
value = textState,
|
||||||
// }
|
placeholder = stringResource(R.string.auth_label),
|
||||||
// }
|
modifier = Modifier
|
||||||
//
|
.testTag(TestIds.Auth.CODE_INPUT)
|
||||||
// Column(
|
.fillMaxWidth(),
|
||||||
// modifier = Modifier
|
onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) }
|
||||||
// .fillMaxSize()
|
)
|
||||||
// .padding(all = 24.dp),
|
|
||||||
// horizontalAlignment = Alignment.CenterHorizontally,
|
if (errorStateValue != "") {
|
||||||
// verticalArrangement = Arrangement.Center
|
BaseText12(
|
||||||
// ) {
|
text = errorStateValue,
|
||||||
// Text(
|
color = Red,
|
||||||
// text = stringResource(R.string.auth_title),
|
modifier = Modifier
|
||||||
// style = MaterialTheme.typography.headlineSmall,
|
.testTag(TestIds.Auth.ERROR)
|
||||||
// textAlign = TextAlign.Center
|
.padding(
|
||||||
// )
|
start = 10.dp,
|
||||||
// when (val currentState = state) {
|
top = 5.dp,
|
||||||
// is AuthState.Data -> Content(viewModel, currentState)
|
bottom = 0.dp
|
||||||
// is AuthState.Loading -> {
|
)
|
||||||
// CircularProgressIndicator(
|
.fillMaxWidth()
|
||||||
// modifier = Modifier.size(64.dp)
|
)
|
||||||
// )
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// }
|
BaseButton(
|
||||||
//}
|
text = stringResource(R.string.auth_sign_in),
|
||||||
//
|
onClick = { viewModel.onIntent(AuthIntent.Send(textState)) },
|
||||||
//@Composable
|
btnColor = Blue,
|
||||||
//private fun Content(
|
enable = isButtonEnabled,
|
||||||
// viewModel: AuthViewModel,
|
btnContentColor = White,
|
||||||
// state: AuthState.Data
|
modifier = Modifier
|
||||||
//) {
|
.testTag(TestIds.Auth.SIGN_BUTTON)
|
||||||
// var inputText by remember { mutableStateOf("") }
|
.fillMaxWidth()
|
||||||
// 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)) }
|
|
||||||
// )
|
|
||||||
// Spacer(modifier = Modifier.size(16.dp))
|
|
||||||
// Button(
|
|
||||||
// modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(),
|
|
||||||
// onClick = {
|
|
||||||
// viewModel.onIntent(AuthIntent.Send(inputText))
|
|
||||||
// },
|
|
||||||
// enabled = true
|
|
||||||
// ) {
|
|
||||||
// Text(stringResource(R.string.auth_sign_in))
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,66 +1,62 @@
|
|||||||
package ru.myitschool.work.ui.screen.auth
|
package ru.myitschool.work.ui.screen.auth
|
||||||
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import android.app.Application
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
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.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.myitschool.work.App
|
||||||
|
import ru.myitschool.work.data.datastore.UserCode
|
||||||
import ru.myitschool.work.data.repo.AuthRepository
|
import ru.myitschool.work.data.repo.AuthRepository
|
||||||
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
|
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)
|
|
||||||
// val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
|
|
||||||
//
|
|
||||||
// private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
|
|
||||||
// val actionFlow: SharedFlow<Unit> = _actionFlow
|
|
||||||
//
|
|
||||||
// fun onIntent(intent: AuthIntent) {
|
|
||||||
// when (intent) {
|
|
||||||
// is AuthIntent.Send -> {
|
|
||||||
// viewModelScope.launch(Dispatchers.Default) {
|
|
||||||
// _uiState.update { AuthState.Loading }
|
|
||||||
// checkAndSaveAuthCodeUseCase.invoke("9999").fold(
|
|
||||||
// onSuccess = {
|
|
||||||
// _actionFlow.emit(Unit)
|
|
||||||
// },
|
|
||||||
// onFailure = { error ->
|
|
||||||
// error.printStackTrace()
|
|
||||||
// _actionFlow.emit(Unit)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// is AuthIntent.TextInput -> Unit
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
private val dataStoreManager by lazy {
|
||||||
|
(getApplication() as App).dataStoreManager
|
||||||
|
}
|
||||||
|
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
|
||||||
|
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 _errorStateValue = MutableStateFlow("")
|
||||||
|
val errorStateValue: StateFlow<String> = _errorStateValue.asStateFlow()
|
||||||
|
private val _isButtonEnabled = MutableStateFlow(false)
|
||||||
|
val isButtonEnabled: StateFlow<Boolean> = _isButtonEnabled.asStateFlow()
|
||||||
private val _textState = MutableStateFlow("")
|
private val _textState = MutableStateFlow("")
|
||||||
val textState: StateFlow<String> = _textState.asStateFlow()
|
val textState: StateFlow<String> = _textState.asStateFlow()
|
||||||
|
|
||||||
private val _errorState = MutableStateFlow(false)
|
fun onIntent(intent: AuthIntent) {
|
||||||
val errorState: StateFlow<Boolean> = _errorState.asStateFlow()
|
when (intent) {
|
||||||
|
is AuthIntent.Send -> {
|
||||||
fun updateText(newText: String) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
_textState.value = newText
|
_uiState.update { AuthState.Loading }
|
||||||
_errorState.value = false
|
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
|
||||||
|
onSuccess = {
|
||||||
|
dataStoreManager.saveUserCode(UserCode(code = intent.text))
|
||||||
|
_actionFlow.emit(Unit)
|
||||||
|
},
|
||||||
|
onFailure = { error ->
|
||||||
|
error.printStackTrace()
|
||||||
|
_uiState.update { AuthState.Data }
|
||||||
|
_errorStateValue.value = "Неизвестная ошибка"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AuthIntent.TextInput -> {
|
||||||
|
_textState.value = intent.text
|
||||||
|
_errorStateValue.value = ""
|
||||||
|
_isButtonEnabled.value = if (intent.text.length == 4 && intent.text.matches(Regex("^[a-zA-Z0-9]*\$")))
|
||||||
|
true else false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isButtonEnabled: StateFlow<Boolean> =
|
|
||||||
_textState.map { it.length == 4 && it.matches(Regex("^[a-zA-Z0-9]*\$"))}
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.WhileSubscribed(5000),
|
|
||||||
initialValue = false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -11,10 +11,14 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -23,17 +27,14 @@ import androidx.compose.ui.res.painterResource
|
|||||||
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.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import ru.myitschool.work.R
|
import ru.myitschool.work.R
|
||||||
import ru.myitschool.work.core.TestIds.Main
|
import ru.myitschool.work.core.TestIds.Main
|
||||||
import ru.myitschool.work.ui.BaseButton
|
import ru.myitschool.work.ui.BaseButton
|
||||||
import ru.myitschool.work.ui.BaseNoBackgroundButton
|
import ru.myitschool.work.ui.BaseNoBackgroundButton
|
||||||
import ru.myitschool.work.ui.BaseText14
|
import ru.myitschool.work.ui.BaseText14
|
||||||
import ru.myitschool.work.ui.BaseText16
|
|
||||||
import ru.myitschool.work.ui.BaseText20
|
import ru.myitschool.work.ui.BaseText20
|
||||||
import ru.myitschool.work.ui.BaseText24
|
|
||||||
import ru.myitschool.work.ui.ErrorScreen
|
|
||||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
|
||||||
import ru.myitschool.work.ui.theme.Black
|
import ru.myitschool.work.ui.theme.Black
|
||||||
import ru.myitschool.work.ui.theme.Blue
|
import ru.myitschool.work.ui.theme.Blue
|
||||||
import ru.myitschool.work.ui.theme.LightGray
|
import ru.myitschool.work.ui.theme.LightGray
|
||||||
@@ -42,8 +43,34 @@ import ru.myitschool.work.ui.theme.White
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
navController: NavController
|
navController: NavController,
|
||||||
|
viewModel: MainViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val state by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
when(state) {
|
||||||
|
is MainState.Loading -> {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(64.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MainState.Error -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
is MainState.Data -> {
|
||||||
|
Content(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content(viewModel: MainViewModel) {
|
||||||
Column (
|
Column (
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -52,98 +79,88 @@ fun MainScreen(
|
|||||||
.width(400.dp)
|
.width(400.dp)
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
val dateError = false
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
if(!dateError) {
|
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
|
||||||
Column(
|
.background(Blue)
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
.fillMaxWidth()
|
||||||
modifier = Modifier
|
.padding(10.dp)
|
||||||
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
|
) {
|
||||||
.background(Blue)
|
Row(
|
||||||
.fillMaxWidth()
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
.padding(10.dp)
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Row(
|
BaseNoBackgroundButton(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
text = "Обновить",
|
||||||
modifier = Modifier.fillMaxWidth()
|
onClick = { },
|
||||||
) {
|
modifier = Modifier.testTag(Main.REFRESH_BUTTON)
|
||||||
|
|
||||||
BaseNoBackgroundButton(
|
|
||||||
text = "Обновить",
|
|
||||||
onClick = { },
|
|
||||||
modifier = Modifier.testTag(Main.REFRESH_BUTTON)
|
|
||||||
)
|
|
||||||
|
|
||||||
BaseNoBackgroundButton(
|
|
||||||
text = "Выйти",
|
|
||||||
onClick = { },
|
|
||||||
modifier = Modifier.testTag(Main.LOGOUT_BUTTON)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.avatar),
|
|
||||||
contentDescription = "User avatar",
|
|
||||||
modifier = Modifier
|
|
||||||
.testTag(Main.PROFILE_IMAGE)
|
|
||||||
.padding(20.dp)
|
|
||||||
)
|
)
|
||||||
|
BaseNoBackgroundButton(
|
||||||
BaseText20(
|
text = "Выйти",
|
||||||
text = "Артемий Артемиев Иванович",
|
onClick = { },
|
||||||
color = White,
|
modifier = Modifier.testTag(Main.LOGOUT_BUTTON)
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.testTag(Main.PROFILE_NAME)
|
|
||||||
.width(250.dp),
|
|
||||||
style = Typography.bodyLarge
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
}
|
}
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
Image(
|
||||||
|
painter = painterResource(R.drawable.avatar),
|
||||||
|
contentDescription = "User avatar",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.testTag(Main.PROFILE_IMAGE)
|
||||||
.padding(20.dp)
|
.padding(20.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
)
|
||||||
.background(White)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Ваши забронированные места",
|
|
||||||
style = Typography.bodyMedium,
|
|
||||||
color = Black,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
horizontal = 10.dp,
|
|
||||||
vertical = 20.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
BookList()
|
|
||||||
}
|
|
||||||
BaseButton(
|
|
||||||
text = "Бронировать",
|
|
||||||
btnColor = Blue,
|
|
||||||
btnContentColor = White,
|
|
||||||
onClick = { navController.navigate(BookScreenDestination)},
|
|
||||||
modifier = Modifier
|
|
||||||
.testTag(Main.ADD_BUTTON)
|
|
||||||
.padding(horizontal = 10.dp, vertical = 15.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
icon = {Image(
|
|
||||||
painter = painterResource(R.drawable.add_icon),
|
|
||||||
contentDescription = "plus Icon"
|
|
||||||
)}
|
|
||||||
|
|
||||||
)
|
BaseText20(
|
||||||
}
|
text = "Артемий Артемиев Иванович",
|
||||||
|
color = White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(Main.PROFILE_NAME)
|
||||||
|
.width(250.dp),
|
||||||
|
style = Typography.bodyLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
}
|
}
|
||||||
else {
|
Column(
|
||||||
ErrorScreen()
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(20.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(White)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Ваши забронированные места",
|
||||||
|
style = Typography.bodyMedium,
|
||||||
|
color = Black,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = 10.dp,
|
||||||
|
vertical = 20.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
BookList()
|
||||||
|
}
|
||||||
|
BaseButton(
|
||||||
|
text = "Бронировать",
|
||||||
|
btnColor = Blue,
|
||||||
|
btnContentColor = White,
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(Main.ADD_BUTTON)
|
||||||
|
.padding(horizontal = 10.dp, vertical = 15.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
icon = {Image(
|
||||||
|
painter = painterResource(R.drawable.add_icon),
|
||||||
|
contentDescription = "plus Icon"
|
||||||
|
)}
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
sealed interface MainState {
|
||||||
|
object Data: MainState
|
||||||
|
object Loading: MainState
|
||||||
|
object Error: MainState
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class MainViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow<MainState>(MainState.Loading)
|
||||||
|
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.splash
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||||
|
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SplashScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: SplashViewModel = viewModel()
|
||||||
|
) {
|
||||||
|
|
||||||
|
val splashState by viewModel.splashState.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(splashState) {
|
||||||
|
when (splashState) {
|
||||||
|
is SplashState.Authenticated -> {
|
||||||
|
navController.navigate(MainScreenDestination)
|
||||||
|
}
|
||||||
|
is SplashState.UnAuthenticated -> {
|
||||||
|
navController.navigate(AuthScreenDestination)
|
||||||
|
}
|
||||||
|
is SplashState.Error -> {
|
||||||
|
navController.navigate(AuthScreenDestination)
|
||||||
|
}
|
||||||
|
SplashState.Loading -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(64.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.splash
|
||||||
|
|
||||||
|
import android.os.Message
|
||||||
|
|
||||||
|
sealed interface SplashState {
|
||||||
|
object Loading: SplashState
|
||||||
|
object Authenticated: SplashState
|
||||||
|
object UnAuthenticated: SplashState
|
||||||
|
class Error(message: String): SplashState
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.splash
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.myitschool.work.App
|
||||||
|
|
||||||
|
class SplashViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val dataStoreManager by lazy {
|
||||||
|
(getApplication() as App).dataStoreManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _splashState = MutableStateFlow<SplashState>(SplashState.Loading)
|
||||||
|
val splashState: StateFlow<SplashState> = _splashState.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
checkAuthStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAuthStatus() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val userCode = dataStoreManager.getUserCode().first()
|
||||||
|
|
||||||
|
val isAuthenticated = when {
|
||||||
|
userCode == null -> false
|
||||||
|
userCode.code is String -> (userCode.code as String).isNotEmpty()
|
||||||
|
userCode.code is Int -> (userCode.code as Int) != -1
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
_splashState.value = if (isAuthenticated) {
|
||||||
|
SplashState.Authenticated
|
||||||
|
} else {
|
||||||
|
SplashState.UnAuthenticated
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_splashState.value = SplashState.Error(e.message ?: "Unknown error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user