global refactoring
This commit is contained in:
@@ -36,7 +36,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
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("androidx.navigation:navigation-compose:2.9.6")
|
||||
val coil = "3.3.0"
|
||||
|
||||
@@ -2,11 +2,21 @@ package ru.myitschool.work
|
||||
|
||||
import android.app.Application
|
||||
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() {
|
||||
|
||||
lateinit var dataStoreManager: DataStoreManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
context = this
|
||||
dataStoreManager = DataStoreManager(dataStore)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
236
app/src/main/java/ru/myitschool/work/ui/Composables.kt
Normal file
236
app/src/main/java/ru/myitschool/work/ui/Composables.kt
Normal file
@@ -0,0 +1,236 @@
|
||||
package ru.myitschool.work.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.core.TestIds.Main
|
||||
import ru.myitschool.work.ui.theme.Black
|
||||
import ru.myitschool.work.ui.theme.Blue
|
||||
import ru.myitschool.work.ui.theme.Gray
|
||||
import ru.myitschool.work.ui.theme.LightBlue
|
||||
import ru.myitschool.work.ui.theme.LightGray
|
||||
import ru.myitschool.work.ui.theme.Typography
|
||||
import ru.myitschool.work.ui.theme.White
|
||||
|
||||
@Composable
|
||||
fun BaseText24(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Black,
|
||||
textAlign: TextAlign = TextAlign.Left
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 24.sp,
|
||||
style = Typography.bodyLarge,
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseNoBackgroundButton(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = White,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
disabledContentColor = White
|
||||
)
|
||||
) {
|
||||
BaseText16(text = text, color = White)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Logo() {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.logo),
|
||||
contentDescription = "Logo",
|
||||
modifier = Modifier.padding(top = 40.dp, bottom = 60.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseText16(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Black,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = Typography.bodySmall,
|
||||
fontSize = 16.sp,
|
||||
color = color,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseText12(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
color: Color = Black,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = Typography.bodySmall,
|
||||
fontSize = 12.sp,
|
||||
color = color,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseText14(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
color: Color = Black,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = Typography.bodySmall,
|
||||
fontSize = 14.sp,
|
||||
color = color,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseInputText(
|
||||
placeholder: String= "",
|
||||
modifier: Modifier = Modifier,
|
||||
onValueChange: (String) -> Unit,
|
||||
value: String
|
||||
) {
|
||||
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
placeholder = {
|
||||
BaseText16(
|
||||
text = placeholder,
|
||||
color = Gray
|
||||
)
|
||||
},
|
||||
textStyle = Typography.bodySmall.copy(fontSize = 16.sp),
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = LightBlue,
|
||||
unfocusedContainerColor = LightBlue,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
errorIndicatorColor = Color.Transparent
|
||||
),
|
||||
singleLine = true,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseText20(
|
||||
text: String,
|
||||
color: Color = Color.Unspecified,
|
||||
style: TextStyle = Typography.bodySmall,
|
||||
modifier: Modifier = Modifier.padding(7.dp),
|
||||
textAlign: TextAlign = TextAlign.Unspecified
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = style,
|
||||
fontSize = 20.sp,
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BaseButton(
|
||||
enable: Boolean = true,
|
||||
text: String,
|
||||
btnColor: Color,
|
||||
btnContentColor: Color,
|
||||
onClick: () -> Unit,
|
||||
icon: @Composable RowScope.() -> Unit = {},
|
||||
modifier: Modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(
|
||||
enabled = enable,
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = btnColor,
|
||||
contentColor = btnContentColor,
|
||||
disabledContainerColor = LightGray,
|
||||
disabledContentColor = Gray
|
||||
),
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
icon()
|
||||
BaseText20(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorScreen() {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 20.dp, vertical = 40.dp)
|
||||
) {
|
||||
|
||||
Spacer(modifier = Modifier.height(80.dp))
|
||||
|
||||
BaseText24(
|
||||
text = "Ошибка загрузки данных",
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.testTag(Main.ERROR)
|
||||
.width(250.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(30.dp))
|
||||
|
||||
BaseButton(
|
||||
modifier = Modifier.testTag(Main.REFRESH_BUTTON),
|
||||
text = "Обновить",
|
||||
btnColor = Blue,
|
||||
btnContentColor = White,
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.myitschool.work.ui.nav
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object SplashScreenDestination: AppDestination
|
||||
@@ -14,7 +14,10 @@ import androidx.navigation.compose.rememberNavController
|
||||
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.nav.SplashScreenDestination
|
||||
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
||||
import ru.myitschool.work.ui.screen.main.MainScreen
|
||||
import ru.myitschool.work.ui.screen.splash.SplashScreen
|
||||
|
||||
@Composable
|
||||
fun AppNavHost(
|
||||
@@ -26,17 +29,16 @@ fun AppNavHost(
|
||||
enterTransition = { EnterTransition.None },
|
||||
exitTransition = { ExitTransition.None },
|
||||
navController = navController,
|
||||
startDestination = AuthScreenDestination,
|
||||
startDestination = SplashScreenDestination,
|
||||
) {
|
||||
composable<SplashScreenDestination> {
|
||||
SplashScreen(navController = navController)
|
||||
}
|
||||
composable<AuthScreenDestination> {
|
||||
AuthScreen(navController = navController)
|
||||
}
|
||||
composable<MainScreenDestination> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "Hello")
|
||||
}
|
||||
MainScreen(navController = navController)
|
||||
}
|
||||
composable<BookScreenDestination> {
|
||||
Box(
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -30,12 +23,20 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.core.TestIds
|
||||
import ru.myitschool.work.ui.BaseButton
|
||||
import ru.myitschool.work.ui.BaseInputText
|
||||
import ru.myitschool.work.ui.BaseText12
|
||||
import ru.myitschool.work.ui.BaseText24
|
||||
import ru.myitschool.work.ui.Logo
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
import ru.myitschool.work.ui.theme.Blue
|
||||
import ru.myitschool.work.ui.theme.Red
|
||||
import ru.myitschool.work.ui.theme.White
|
||||
|
||||
@Composable
|
||||
fun AuthScreen(
|
||||
viewModel: AuthViewModel = viewModel(),
|
||||
navController: NavController
|
||||
navController: NavController,
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsState()
|
||||
|
||||
@@ -45,24 +46,34 @@ fun AuthScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
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 -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(64.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(400.dp)
|
||||
.fillMaxHeight()
|
||||
.padding(20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
Logo()
|
||||
|
||||
BaseText24(
|
||||
text = stringResource(R.string.auth_title),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
when (state) {
|
||||
is AuthState.Data -> Content(viewModel)
|
||||
is AuthState.Loading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(top = 40.dp)
|
||||
.size(64.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,28 +81,50 @@ fun AuthScreen(
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
viewModel: AuthViewModel,
|
||||
state: AuthState.Data
|
||||
viewModel: AuthViewModel
|
||||
) {
|
||||
var inputText by remember { mutableStateOf("") }
|
||||
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
|
||||
|
||||
val isButtonEnabled by viewModel.isButtonEnabled.collectAsState()
|
||||
val errorStateValue by viewModel.errorStateValue.collectAsState()
|
||||
val textState by viewModel.textState.collectAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 20.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.auth_sign_in))
|
||||
|
||||
BaseInputText(
|
||||
value = textState,
|
||||
placeholder = stringResource(R.string.auth_label),
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Auth.CODE_INPUT)
|
||||
.fillMaxWidth(),
|
||||
onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) }
|
||||
)
|
||||
|
||||
if (errorStateValue != "") {
|
||||
BaseText12(
|
||||
text = errorStateValue,
|
||||
color = Red,
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Auth.ERROR)
|
||||
.padding(
|
||||
start = 10.dp,
|
||||
top = 5.dp,
|
||||
bottom = 0.dp
|
||||
)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BaseButton(
|
||||
text = stringResource(R.string.auth_sign_in),
|
||||
onClick = { viewModel.onIntent(AuthIntent.Send(textState)) },
|
||||
btnColor = Blue,
|
||||
enable = isButtonEnabled,
|
||||
btnContentColor = White,
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Auth.SIGN_BUTTON)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package ru.myitschool.work.ui.screen.auth
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -10,34 +11,52 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
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.domain.auth.CheckAndSaveAuthCodeUseCase
|
||||
|
||||
class AuthViewModel : ViewModel() {
|
||||
class AuthViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
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("")
|
||||
val textState: StateFlow<String> = _textState.asStateFlow()
|
||||
|
||||
fun onIntent(intent: AuthIntent) {
|
||||
when (intent) {
|
||||
is AuthIntent.Send -> {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
_uiState.update { AuthState.Loading }
|
||||
checkAndSaveAuthCodeUseCase.invoke("9999").fold(
|
||||
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
|
||||
onSuccess = {
|
||||
dataStoreManager.saveUserCode(UserCode(code = intent.text))
|
||||
_actionFlow.emit(Unit)
|
||||
},
|
||||
onFailure = { error ->
|
||||
error.printStackTrace()
|
||||
_actionFlow.emit(Unit)
|
||||
_uiState.update { AuthState.Data }
|
||||
_errorStateValue.value = "Неизвестная ошибка"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is AuthIntent.TextInput -> Unit
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package ru.myitschool.work.ui.screen.main
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import ru.myitschool.work.R
|
||||
import ru.myitschool.work.core.TestIds.Main
|
||||
import ru.myitschool.work.ui.BaseButton
|
||||
import ru.myitschool.work.ui.BaseNoBackgroundButton
|
||||
import ru.myitschool.work.ui.BaseText14
|
||||
import ru.myitschool.work.ui.BaseText20
|
||||
import ru.myitschool.work.ui.theme.Black
|
||||
import ru.myitschool.work.ui.theme.Blue
|
||||
import ru.myitschool.work.ui.theme.LightGray
|
||||
import ru.myitschool.work.ui.theme.Typography
|
||||
import ru.myitschool.work.ui.theme.White
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
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 (
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.background(LightGray)
|
||||
.fillMaxSize()
|
||||
.width(400.dp)
|
||||
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
|
||||
.background(Blue)
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
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)
|
||||
)
|
||||
|
||||
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))
|
||||
}
|
||||
Column(
|
||||
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"
|
||||
)}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bookListData = listOf(
|
||||
"Конгресс Холл",
|
||||
"Конгресс Холл",
|
||||
"Конгресс Холл"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun BookList() {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
) {
|
||||
for ((index, book) in bookListData.withIndex()) {
|
||||
BookListElement(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookListElement(index: Int) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.testTag(Main.getIdItemByPosition(index))
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 20.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
BaseText14(
|
||||
text = "Конгресс Холл",
|
||||
modifier = Modifier.testTag(Main.ITEM_PLACE)
|
||||
)
|
||||
BaseText14(
|
||||
text = "16.02.3026",
|
||||
modifier = Modifier.testTag(Main.ITEM_DATE)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,18 @@ val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
|
||||
val Blue = Color(0xFF004BFF)
|
||||
|
||||
val Gray = Color(0xFF777777)
|
||||
|
||||
val LightBlue = Color(0xFFF2EFFF)
|
||||
|
||||
val White = Color(0xFFFFFFFF)
|
||||
|
||||
val Red = Color(0xFFFF4D4D)
|
||||
|
||||
val LightGray = Color(0xFFF2F1F7)
|
||||
|
||||
val Black = Color(0xFF000000)
|
||||
@@ -2,19 +2,34 @@ package ru.myitschool.work.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import ru.myitschool.work.R
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
|
||||
val MontserratFontFamily = FontFamily(
|
||||
Font(R.font.montserrat_bold, FontWeight.Bold),
|
||||
Font(R.font.montserrat_medium, FontWeight.Medium),
|
||||
Font(R.font.montserrat_semibold, weight = FontWeight.SemiBold)
|
||||
)
|
||||
|
||||
val Typography = Typography(
|
||||
bodySmall = TextStyle(
|
||||
fontFamily = MontserratFontFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = MontserratFontFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
fontFamily = MontserratFontFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
),
|
||||
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
|
||||
11
app/src/main/res/drawable/add_icon.xml
Normal file
11
app/src/main/res/drawable/add_icon.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="17dp"
|
||||
android:height="17dp"
|
||||
android:viewportWidth="17"
|
||||
android:viewportHeight="17">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8.296,0.5C8.503,0.5 8.656,0.565 8.786,0.695C8.916,0.826 8.981,0.977 8.98,1.183V7.61H15.405C15.615,7.61 15.767,7.676 15.896,7.805C16.026,7.934 16.09,8.086 16.09,8.294C16.089,8.503 16.024,8.657 15.894,8.788C15.767,8.917 15.616,8.981 15.407,8.98H8.98V15.405C8.98,15.615 8.914,15.767 8.785,15.896C8.656,16.026 8.504,16.09 8.296,16.09C8.087,16.089 7.934,16.024 7.805,15.894C7.676,15.766 7.61,15.615 7.61,15.405V8.98H1.185C0.975,8.98 0.823,8.915 0.695,8.786C0.566,8.656 0.5,8.503 0.5,8.294C0.5,8.086 0.565,7.935 0.694,7.806C0.825,7.675 0.978,7.61 1.185,7.61H7.61V1.185C7.61,0.975 7.676,0.824 7.805,0.695C7.934,0.566 8.087,0.501 8.296,0.5Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#ffffff"/>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/avatar.xml
Normal file
14
app/src/main/res/drawable/avatar.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100">
|
||||
<path
|
||||
android:pathData="M50,50m-48.5,0a48.5,48.5 0,1 1,97 0a48.5,48.5 0,1 1,-97 0"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#AEC3FF"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M61.99,54.17C64.58,54.17 66.67,56.26 66.67,58.85V60.05C66.67,61.91 66.01,63.71 64.8,65.13C61.53,68.95 56.55,70.83 50,70.83C43.45,70.83 38.48,68.95 35.21,65.13C34,63.71 33.34,61.91 33.34,60.05V58.85C33.34,56.26 35.44,54.17 38.03,54.17H61.99ZM61.99,57.29H38.03C37.16,57.29 36.47,57.99 36.47,58.85V60.05C36.47,61.17 36.86,62.25 37.59,63.1C40.2,66.16 44.3,67.71 50,67.71C55.71,67.71 59.8,66.16 62.42,63.1C63.15,62.25 63.55,61.17 63.55,60.05V58.85C63.55,57.99 62.85,57.29 61.99,57.29ZM50,29.18C55.75,29.18 60.42,33.84 60.42,39.59C60.42,45.35 55.75,50.01 50,50.01C44.25,50.01 39.58,45.35 39.58,39.59C39.58,33.84 44.25,29.18 50,29.18ZM50,32.3C45.97,32.3 42.71,35.57 42.71,39.59C42.71,43.62 45.97,46.88 50,46.88C54.03,46.88 57.29,43.62 57.29,39.59C57.29,35.57 54.03,32.3 50,32.3Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
21
app/src/main/res/drawable/logo.xml
Normal file
21
app/src/main/res/drawable/logo.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="53dp"
|
||||
android:height="99dp"
|
||||
android:viewportWidth="53"
|
||||
android:viewportHeight="99">
|
||||
<path
|
||||
android:pathData="M33.501,13.467V35.047H52.507C52.507,24.867 44.587,16.26 33.501,13.467Z"
|
||||
android:fillColor="#004BFF"/>
|
||||
<path
|
||||
android:pathData="M0.267,61.92C0.267,72.08 8.187,80.713 19.274,83.507V61.92H0.267Z"
|
||||
android:fillColor="#004BFF"/>
|
||||
<path
|
||||
android:pathData="M52.781,61.92C52.507,46.767 36.027,41.173 28.594,39.6C25.087,38.84 18.814,36.933 19.007,33.713V13.44C8.621,16.053 -0.293,24.873 0.007,35.473C1.714,53.393 19.207,55.613 29.341,58.88C32.581,60.153 33.861,61.78 33.781,63.333V83.513C44.041,80.993 52.901,72.16 52.781,61.92Z"
|
||||
android:fillColor="#004BFF"/>
|
||||
<path
|
||||
android:pathData="M26.374,85.92C22.941,85.92 20.047,84.867 19.234,83.54L26.374,98.3L33.754,83.513C32.714,84.813 29.821,85.92 26.374,85.92Z"
|
||||
android:fillColor="#004BFF"/>
|
||||
<path
|
||||
android:pathData="M26.661,13.467C29.821,13.467 32.467,12.407 32.467,10.8V2.62C32.467,1.32 29.801,0 26.661,0C23.221,0 20.601,1.053 20.601,2.667V10.813C20.574,12.14 23.221,13.467 26.661,13.467Z"
|
||||
android:fillColor="#004BFF"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/font/montserrat_bold.ttf
Normal file
BIN
app/src/main/res/font/montserrat_bold.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/montserrat_medium.ttf
Normal file
BIN
app/src/main/res/font/montserrat_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/montserrat_semibold.ttf
Normal file
BIN
app/src/main/res/font/montserrat_semibold.ttf
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">Work</string>
|
||||
<string name="title_activity_root">RootActivity</string>
|
||||
<string name="auth_title">Привет! Введи код для авторизации</string>
|
||||
<string name="auth_title">Введите код для авторизации</string>
|
||||
<string name="auth_label">Код</string>
|
||||
<string name="auth_sign_in">Войти</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user