Compare commits

...

2 Commits

Author SHA1 Message Date
48f4f9fd6f global refactoring 2025-11-30 00:36:17 +03:00
ca1850b97a global refactoring 2025-11-30 00:33:07 +03:00
15 changed files with 426 additions and 274 deletions

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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] ?: ""
)
}
}

View File

@@ -0,0 +1,5 @@
package ru.myitschool.work.data.datastore
data class UserCode(
val code: String
)

View File

@@ -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(

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.nav
import kotlinx.serialization.Serializable
@Serializable
data object SplashScreenDestination: AppDestination

View File

@@ -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")
}
} }
} }
} }

View File

@@ -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))
// }
//}

View File

@@ -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
)
} }

View File

@@ -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"
)}
)
} }
} }
} }

View File

@@ -0,0 +1,7 @@
package ru.myitschool.work.ui.screen.main
sealed interface MainState {
object Data: MainState
object Loading: MainState
object Error: MainState
}

View File

@@ -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()
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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")
}
}
}
}