Compare commits

12 Commits
main ... main

Author SHA1 Message Date
solovushka56
6f2ec1fefd bugfix 2025-12-12 14:56:24 +03:00
solovushka56
c6418793cb fix revert 2025-12-12 12:59:43 +03:00
solovushka56
2da3b58773 Revert "fix"
This reverts commit a04fb915ae034c98d6e74759c8e823ab52ec03e1.
2025-12-12 12:34:33 +03:00
solovushka56
a04fb915ae fix 2025-12-12 12:03:06 +03:00
solovushka56
fa68925205 viewmodel fix 2025-12-12 10:42:32 +03:00
solovushka56
011e169f96 bugfix 2025-12-12 02:20:05 +03:00
solovushka56
cd70c1cd20 booking fix + ui 2025-12-11 22:48:32 +03:00
solovushka56
79650a94e8 booking fix (1) 2025-12-11 20:26:18 +03:00
solovushka56
2af2c36ab2 booking fix 2025-12-11 20:03:13 +03:00
solovushka56
87bbfcc96c singleton (AuthRepository) repo refactor 2025-12-11 19:29:55 +03:00
solovushka56
b74a74f3bd navigation fix + code review 2025-12-11 17:06:05 +03:00
solovushka56
d8416283ae base functional added 2025-12-11 01:51:17 +03:00
81 changed files with 2451 additions and 107 deletions

View File

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

View File

@@ -0,0 +1,35 @@
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 clearUserCode() {
dataStore.edit { preferences ->
preferences.remove(USER_CODE_KEY)
}
}
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

@@ -4,13 +4,7 @@ import ru.myitschool.work.data.source.NetworkDataSource
object AuthRepository {
private var codeCache: String? = null
suspend fun checkAndSave(text: String): Result<Boolean> {
return NetworkDataSource.checkAuth(text).onSuccess { success ->
if (success) {
codeCache = text
}
}
suspend fun checkAndSave(code: String): Result<Boolean> {
return NetworkDataSource.checkAuth(code)
}
}
}

View File

@@ -0,0 +1,25 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.source.NetworkDataSource
import ru.myitschool.work.domain.book.entities.BookingEntity
object ReserveRepo {
suspend fun loadAvailable(code: String): Result<BookingEntity> {
return NetworkDataSource.loadBooking(code)
}
suspend fun reserve(
userCode: String,
day: String,
placeId: Int,
placeLabel: String
): Result<Unit> {
return NetworkDataSource.bookPlace(
userCode = userCode,
date = day,
placeId = placeId,
placeName = placeLabel
)
}
}

View File

@@ -0,0 +1,11 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.source.NetworkDataSource
import ru.myitschool.work.domain.main.entities.UserEntity
object ProfileRepo {
suspend fun loadProfile(code: String): Result<UserEntity> {
return NetworkDataSource.loadData(code)
}
}

View File

@@ -1,9 +1,13 @@
package ru.myitschool.work.data.source
import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
@@ -11,6 +15,30 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import ru.myitschool.work.core.Constants
import ru.myitschool.work.domain.book.entities.BookingEntity
import ru.myitschool.work.domain.book.entities.PlaceInfo
import ru.myitschool.work.domain.main.entities.UserEntity
private const val testJson = """
{
"name": "Иванов Петр Федорович",
"photoUrl": "https://funnyducks.ru/upload/iblock/0cd/0cdeb7ec3ed6fddda0f90fccee05557d.jpg",
"booking": {
"2025-01-05": {"id":1,"place":"102"},
"2025-01-06": {"id":2,"place":"209.13"},
"2025-01-09": {"id":3,"place":"Зона 51. 50"}
}
}
"""
private const val testBookingJson = """
{
"2025-01-05": [{"id": 1, "place": "102"},{"id": 2, "place": "209.13"}],
"2025-01-06": [{"id": 3, "place": "Зона 51. 50"}],
"2025-01-07": [{"id": 1, "place": "102"},{"id": 2, "place": "209.13"}],
"2025-01-08": [{"id": 2, "place": "209.13"}]
}
"""
object NetworkDataSource {
private val client by lazy {
@@ -28,9 +56,38 @@ object NetworkDataSource {
}
}
suspend fun bookPlace(
userCode: String,
date: String,
placeId: Int,
placeName: String
): Result<Unit> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// Log.i("aaa", "Booking: userCode=$userCode, date=$date, placeId=$placeId, placeName=$placeName")
// println("Booking: userCode=$userCode, date=$date, placeId=$placeId, placeName=$placeName")
val response = client.post(getUrl(userCode, Constants.BOOK_URL)) {
setBody(mapOf(
"date" to date,
"placeId" to placeId,
"placeName" to placeName
))
}
when (response.status) {
HttpStatusCode.OK -> Unit
else -> error(response.bodyAsText())
}
}
}
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// true // удалить при проверке
val response = client.get(getUrl(code, Constants.AUTH_URL))
response.status
when (response.status) {
HttpStatusCode.OK -> true
else -> error(response.bodyAsText())
@@ -38,5 +95,35 @@ object NetworkDataSource {
}
}
suspend fun loadData(code: String): Result<UserEntity> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// Json.decodeFromString<UserEntity>(testJson) // удалить при проверке
val response = client.get(getUrl(code, Constants.INFO_URL))
when (response.status) {
HttpStatusCode.OK -> {
response.body<UserEntity>()
}
else -> error(response.bodyAsText())
}
}
}
suspend fun loadBooking(code: String): Result<BookingEntity> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// BookingEntity(Json.decodeFromString<Map<String, List<PlaceInfo>>>(testBookingJson)) // удалить при проверке
val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) {
HttpStatusCode.OK -> {
BookingEntity(response.body<Map<String, List<PlaceInfo>>>())
}
else -> error(response.bodyAsText())
}
}
}
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
}

View File

@@ -0,0 +1,16 @@
package ru.myitschool.work.domain.book
import ru.myitschool.work.data.repo.BookRepository
class BookingUseCase(
private val repository: BookRepository
) {
suspend operator fun invoke(
userCode: String,
date: String,
placeId: Int,
placeName: String
): Result<Unit> {
return repository.bookPlace(userCode, date, placeId, placeName)
}
}

View File

@@ -0,0 +1,16 @@
package ru.myitschool.work.domain.book
import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.book.entities.BookingEntity
import ru.myitschool.work.domain.main.entities.UserEntity
class LoadBookingUseCase(
private val repository: BookRepository
) {
suspend operator fun invoke(
text: String
): Result<BookingEntity> {
return repository.loadBooking(text)
}
}

View File

@@ -0,0 +1,8 @@
package ru.myitschool.work.domain.book.entities
import kotlinx.serialization.Serializable
@Serializable
data class BookingEntity(
val bookings: Map<String, List<PlaceInfo>>
)

View File

@@ -0,0 +1,9 @@
package ru.myitschool.work.domain.book.entities
import kotlinx.serialization.Serializable
@Serializable
data class PlaceInfo(
val id: Int,
val place: String
)

View File

@@ -0,0 +1,14 @@
package ru.myitschool.work.domain.main
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.main.entities.UserEntity
class LoadDataUseCase(
private val repository: MainRepository
) {
suspend operator fun invoke(
text: String
): Result<UserEntity> {
return repository.loadData(text)
}
}

View File

@@ -0,0 +1,9 @@
package ru.myitschool.work.domain.main.entities
import kotlinx.serialization.Serializable
@Serializable
data class BookingInfo(
val id: Int,
val place: String
)

View File

@@ -0,0 +1,26 @@
package ru.myitschool.work.domain.main.entities
import kotlinx.serialization.Serializable
import ru.myitschool.work.formatDate
@Serializable
data class UserEntity(
val name: String,
val photoUrl: String,
val booking: Map<String, BookingInfo>? = null
) {
fun getSortedBookings(): List<Pair<String, BookingInfo>> {
return booking?.entries
?.sortedBy { (date, _) -> date }
?.map { it.toPair() }
?: emptyList()
}
fun getSortedBookingsWithFormattedDate(): List<Triple<String, String, BookingInfo>> {
return getSortedBookings().map { (date, bookingInfo) ->
Triple(date, date.formatDate(), bookingInfo)
}
}
fun hasBookings(): Boolean = !booking.isNullOrEmpty()
}

View File

@@ -0,0 +1,198 @@
package ru.myitschool.work.ui
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.ui.Modifier
import androidx.compose.ui.graphics.Color
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.ui.theme.Black
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 BaseButton(
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.ic_git_clone_mini),
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(
border: BorderStroke? = null,
enable: Boolean = true,
text: String,
btnColor: Color,
btnContentColor: Color,
onClick: () -> Unit,
icon: @Composable RowScope.() -> Unit = {},
modifier: Modifier = Modifier.fillMaxWidth()
) {
Button(
border = border,
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)
}
}

View File

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

View File

@@ -14,7 +14,11 @@ 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.book.BookScreen
import ru.myitschool.work.ui.screen.main.MainScreen
import ru.myitschool.work.ui.screen.splash.SplashScreen
@Composable
fun AppNavHost(
@@ -26,24 +30,19 @@ 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(
contentAlignment = Alignment.Center
) {
Text(text = "Hello")
}
BookScreen(navController = navController)
}
}
}

View File

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

View File

@@ -1,43 +1,62 @@
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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
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() {
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data)
val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
class LoginViewModel(application: Application) : AndroidViewModel(application) {
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
private val store by lazy { (getApplication() as App).dataStoreManager }
fun onIntent(intent: AuthIntent) {
private val _screenState = MutableStateFlow<LoginState>(LoginState.Ready)
val screenState: StateFlow<LoginState> = _screenState.asStateFlow()
private val _navigate = MutableSharedFlow<Unit>()
val navigate: SharedFlow<Unit> = _navigate
private val _input = MutableStateFlow("")
val input: StateFlow<String> get() = _input
private val _error = MutableStateFlow("")
val error: StateFlow<String> get() = _error
val isValid = input.map { it.length == 4 && it.matches(Regex("[A-Za-z0-9]+")) }
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
fun perform(intent: LoginIntent) {
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 LoginIntent.Type -> {
_input.value = intent.value
_error.value = ""
}
is LoginIntent.Submit -> {
authorize()
}
is AuthIntent.TextInput -> Unit
}
}
}
private fun authorize() {
viewModelScope.launch(Dispatchers.IO) {
_screenState.value = LoginState.Loading
AuthRepository.validateCode(_input.value).fold(
onSuccess = {
store.saveUserCode(UserCode(_input.value))
_navigate.emit(Unit)
},
onFailure = { err ->
_screenState.value = LoginState.Ready
_error.value = err.message ?: "Ошибка"
}
)
}
}
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookAction {
object Auth: BookAction
object Main: BookAction
}

View File

@@ -0,0 +1,12 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookIntent {
object Back: BookIntent
object LoadBooking: BookIntent
object Book : BookIntent
data class SelectDate(val date: String) : BookIntent
data class SelectPlace(
val placeId: Int,
val placeName: String
) : BookIntent
}

View File

@@ -0,0 +1,415 @@
package ru.myitschool.work.ui.screen.book
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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.Book
import ru.myitschool.work.domain.book.entities.BookingEntity
import ru.myitschool.work.domain.book.entities.PlaceInfo
import ru.myitschool.work.formatBookingDate
import ru.myitschool.work.ui.BaseButton
import ru.myitschool.work.ui.BaseText16
import ru.myitschool.work.ui.BaseText24
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.theme.Black
import ru.myitschool.work.ui.theme.Blue
import ru.myitschool.work.ui.theme.Typography
import ru.myitschool.work.ui.theme.White
@Composable
fun BookScreen(
navController: NavController,
viewModel: BookViewModel = viewModel(),
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when(action) {
is BookAction.Auth -> navController.navigate(AuthScreenDestination)
is BookAction.Main -> navController.navigate(MainScreenDestination)
}
}
}
when(state) {
is BookState.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
}
}
is BookState.Data -> {
val dataState = state as BookState.Data
DataContent(
viewModel = viewModel,
bookingData = dataState.userBooking,
selectedDate = dataState.selectedDate,
selectedPlaceId = dataState.selectedPlaceId
)
}
is BookState.Error -> ErrorContent(viewModel)
is BookState.Empty -> EmptyContent(viewModel)
}
}
@Composable
fun EmptyContent(
viewModel: BookViewModel
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.book_all_booked),
modifier = Modifier.testTag(Book.EMPTY),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
text = stringResource(R.string.book_back),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
@Composable
fun ErrorContent(
viewModel: BookViewModel
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.book_error),
modifier = Modifier.testTag(Book.ERROR),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
border = BorderStroke(1.dp, Blue),
text = stringResource(R.string.book_back),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = Blue,
btnColor = Color.Transparent
)
Spacer(modifier = Modifier.height(15.dp))
BaseButton(
text = stringResource(R.string.main_update),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.REFRESH_BUTTON),
onClick = { viewModel.onIntent(BookIntent.LoadBooking) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
@Composable
fun DataContent(
viewModel: BookViewModel,
bookingData: BookingEntity,
selectedDate: String,
selectedPlaceId: Int
) {
val availableDates = bookingData.bookings
.filter { it.value.isNotEmpty() }
.keys
.sorted()
val placesForSelectedDate = bookingData.bookings[selectedDate] ?: emptyList()
Column {
Row(
modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
.background(Blue)
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BaseText24(
text = stringResource(R.string.book_new_book),
color = White,
modifier = Modifier.padding(start = 15.dp)
)
BaseButton(
text = stringResource(R.string.book_back),
modifier = Modifier.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) }
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 20.dp, horizontal = 10.dp)
.clip(RoundedCornerShape(16.dp))
.background(White)
) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(13.dp)
) {
Column {
Text(
text = stringResource(R.string.book_available_date),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BookDateList(
dates = availableDates,
selectedDate = selectedDate,
onDateSelected = { date ->
viewModel.onIntent(BookIntent.SelectDate(date))
}
)
Text(
text = stringResource(R.string.book_choose_place),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BookPlaceList(
places = placesForSelectedDate,
selectedPlaceId = selectedPlaceId,
onPlaceSelected = { placeId, placeName ->
viewModel.onIntent(BookIntent.SelectPlace(placeId, placeName))
}
)
}
BaseButton(
enable = selectedPlaceId != -1,
text = stringResource(R.string.booking_button),
btnColor = Blue,
btnContentColor = White,
onClick = { viewModel.onIntent(BookIntent.Book) },
modifier = Modifier
.testTag(Book.BOOK_BUTTON)
.padding(horizontal = 10.dp)
.fillMaxWidth(),
)
}
}
}
}
@Composable
fun BookPlaceList(
places: List<PlaceInfo>,
selectedPlaceId: Int,
onPlaceSelected: (Int, String) -> Unit
) {
Column(
modifier = Modifier.padding(vertical = 15.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
if (places.isEmpty()) {
Text(
text = "Нет доступных мест для выбранной даты",
color = Color.Gray,
style = Typography.bodyMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
} else {
places.forEachIndexed { index, placeInfo ->
BookPlaceListElement(
placeInfo = placeInfo,
isSelected = placeInfo.id == selectedPlaceId,
onPlaceSelected = { onPlaceSelected(placeInfo.id, placeInfo.place) },
index = index
)
}
}
}
}
@Composable
fun BookPlaceListElement(
placeInfo: PlaceInfo,
isSelected: Boolean,
onPlaceSelected: () -> Unit,
index: Int
) {
Row(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = isSelected,
onClick = onPlaceSelected
)
.testTag(Book.getIdPlaceItemByPosition(index))
.padding(vertical = 12.dp, horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
BaseText16(
text = placeInfo.place,
modifier = Modifier.testTag(Book.ITEM_PLACE_TEXT)
)
Box(
modifier = Modifier
.size(24.dp)
.border(
width = 2.dp,
color = if (isSelected) Blue else Color.Gray,
shape = CircleShape
)
.background(
color = if (isSelected) Blue else Color.Transparent,
shape = CircleShape
)
.testTag(Book.ITEM_PLACE_SELECTOR)
) {
if (isSelected) {
Box(
modifier = Modifier
.size(12.dp)
.background(Color.White, CircleShape)
.align(Alignment.Center)
)
}
}
}
}
@Composable
fun BookDateList(
dates: List<String>,
selectedDate: String,
onDateSelected: (String) -> Unit
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(7.dp),
modifier = Modifier.padding(vertical = 15.dp)
) {
dates.forEachIndexed { index, date ->
BookDateListElement(
date = date,
isSelected = date == selectedDate,
onClick = { onDateSelected(date) },
index = index
)
}
}
}
@Composable
fun BookDateListElement(
date: String,
isSelected: Boolean,
onClick: () -> Unit,
index: Int
) {
Button(
contentPadding = PaddingValues(0.dp),
modifier = Modifier
.testTag(Book.getIdDateItemByPosition(index))
.padding(0.dp),
border = BorderStroke(1.dp, if (isSelected) Blue else Black,),
onClick = onClick,
colors = ButtonColors(
contentColor = if (isSelected) White else Black,
containerColor = if (isSelected) Blue else Color.Transparent,
disabledContentColor = Black,
disabledContainerColor = Color.Transparent),
) {
val formattedDate = date.formatBookingDate()
BaseText16(
text = formattedDate,
modifier = Modifier.testTag(Book.ITEM_DATE),
color = if (isSelected) White else Black,
)
}
}

View File

@@ -0,0 +1,15 @@
package ru.myitschool.work.ui.screen.book
import ru.myitschool.work.domain.book.entities.BookingEntity
sealed interface BookState {
object Loading: BookState
data class Data(
val userBooking: BookingEntity,
val selectedDate: String = "",
val selectedPlaceId: Int = -1,
val selectedPlaceName: String = ""
): BookState
object Error: BookState
object Empty: BookState
}

View File

@@ -0,0 +1,165 @@
package ru.myitschool.work.ui.screen.book
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.App
import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.book.BookingUseCase
import ru.myitschool.work.domain.book.LoadBookingUseCase
import ru.myitschool.work.domain.main.LoadDataUseCase
import ru.myitschool.work.ui.screen.main.MainAction
import ru.myitschool.work.ui.screen.main.MainIntent
import ru.myitschool.work.ui.screen.main.MainState
import kotlin.text.isEmpty
class BookViewModel(application: Application) : AndroidViewModel(application) {
private val loadBookingUseCase by lazy { LoadBookingUseCase(BookRepository) }
private val bookingUseCase by lazy { BookingUseCase (BookRepository) }
private val dataStoreManager by lazy {
(getApplication() as App).dataStoreManager
}
private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<BookAction> = MutableSharedFlow()
val actionFlow: SharedFlow<BookAction> = _actionFlow
init {
loadBooking()
}
private fun bookSelectedPlace() {
viewModelScope.launch(Dispatchers.IO) {
try {
val userCode = dataStoreManager.getUserCode().first()
val currentState = _uiState.value
if (currentState is BookState.Data && currentState.selectedPlaceId != -1) {
bookingUseCase.invoke(
userCode = userCode.code,
date = currentState.selectedDate,
placeId = currentState.selectedPlaceId,
placeName = currentState.selectedPlaceName
).fold(
onSuccess = {
_actionFlow.emit(BookAction.Main)
},
onFailure = { error ->
error.printStackTrace()
_uiState.update { BookState.Error }
}
)
}
} catch (error: Exception) {
error.printStackTrace()
_uiState.update { BookState.Error }
}
}
}
private fun loadBooking() {
viewModelScope.launch(Dispatchers.IO) {
_uiState.update { BookState.Loading }
try {
val userCode = dataStoreManager.getUserCode().first()
if (userCode.code.isEmpty()) {
_actionFlow.emit(BookAction.Auth)
return@launch
}
loadBookingUseCase.invoke(userCode.code).fold(
onSuccess = { data ->
val availableDates = data.bookings
.filter { it.value.isNotEmpty() }
.keys
.sorted()
if (availableDates.isEmpty()) {
_uiState.update { BookState.Empty }
} else {
val selectedDate = availableDates.first()
val placesForSelectedDate = data.bookings[selectedDate] ?: emptyList()
val selectedPlaceId = placesForSelectedDate.firstOrNull()?.id ?: -1
val selectedPlaceName = placesForSelectedDate.firstOrNull()?.place ?: ""
_uiState.update {
BookState.Data(
userBooking = data,
selectedDate = selectedDate,
selectedPlaceId = selectedPlaceId,
selectedPlaceName = selectedPlaceName
)
}
}
},
onFailure = { error ->
error.printStackTrace()
_uiState.update { BookState.Error }
}
)
} catch (error: Exception) {
error.printStackTrace()
_uiState.update { BookState.Error }
}
}
}
fun onIntent(intent: BookIntent) {
when (intent) {
is BookIntent.LoadBooking -> loadBooking()
is BookIntent.Back -> {
viewModelScope.launch(Dispatchers.Default) {
_actionFlow.emit(BookAction.Main)
}
}
is BookIntent.Book -> bookSelectedPlace()
is BookIntent.SelectDate -> {
val currentState = _uiState.value
if (currentState is BookState.Data) {
val placesForDate =
currentState.userBooking.bookings[intent.date] ?: emptyList()
val newSelectedPlaceId = placesForDate.firstOrNull()?.id ?: -1
val newSelectedPlaceName = placesForDate.firstOrNull()?.place ?: ""
_uiState.update {
currentState.copy(
selectedDate = intent.date,
selectedPlaceId = newSelectedPlaceId,
selectedPlaceName = newSelectedPlaceName
)
}
}
}
is BookIntent.SelectPlace -> {
val currentState = _uiState.value
if (currentState is BookState.Data) {
_uiState.update {
currentState.copy(
selectedPlaceId = intent.placeId,
selectedPlaceName = intent.placeName
)
}
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.main
sealed interface MainAction {
object Booking: MainAction
object Auth: MainAction
}

View File

@@ -0,0 +1,7 @@
package ru.myitschool.work.ui.screen.main
sealed interface MainIntent {
object Logout: MainIntent
object Booking: MainIntent
object LoadData: MainIntent
}

View File

@@ -0,0 +1,301 @@
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.fillMaxHeight
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.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
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.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.res.stringResource
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 coil3.compose.rememberAsyncImagePainter
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds.Main
import ru.myitschool.work.domain.main.entities.BookingInfo
import ru.myitschool.work.domain.main.entities.UserEntity
import ru.myitschool.work.ui.BaseButton
import ru.myitschool.work.ui.BaseText14
import ru.myitschool.work.ui.BaseText16
import ru.myitschool.work.ui.BaseText20
import ru.myitschool.work.ui.BaseText24
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.BookScreenDestination
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()
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when(action) {
is MainAction.Auth -> navController.navigate(AuthScreenDestination)
is MainAction.Booking -> navController.navigate(BookScreenDestination)
}
}
}
when(state) {
is MainState.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
}
}
is MainState.Error -> {
ErrorContent(viewModel)
}
is MainState.Data -> {
DataContent(
viewModel,
userData = (state as MainState.Data).userData
)
}
}
}
@Composable
fun ErrorContent(viewModel: MainViewModel){
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.data_error_message),
modifier = Modifier.testTag(Main.ERROR),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
text = stringResource(R.string.main_update),
modifier = Modifier
.fillMaxWidth()
.testTag(Main.REFRESH_BUTTON),
onClick = { viewModel.onIntent(MainIntent.LoadData) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
@Composable
fun DataContent(
viewModel: MainViewModel,
userData: UserEntity
) {
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()
) {
BaseButton(
text = stringResource(R.string.main_update),
onClick = { viewModel.onIntent(MainIntent.LoadData) },
modifier = Modifier.testTag(Main.REFRESH_BUTTON)
)
BaseButton(
text = stringResource(R.string.main_log_out),
onClick = { viewModel.onIntent(MainIntent.Logout) },
modifier = Modifier.testTag(Main.LOGOUT_BUTTON)
)
}
Image(
painter = rememberAsyncImagePainter(
model = userData.photoUrl,
error = painterResource(R.drawable.github)
),
contentDescription = stringResource(R.string.main_avatar_description),
modifier = Modifier
.clip(RoundedCornerShape(999.dp))
.testTag(Main.PROFILE_IMAGE)
.width(150.dp)
.height(150.dp)
.padding(20.dp)
)
BaseText20(
text = userData.name,
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 = stringResource(R.string.main_booking_title),
style = Typography.bodyMedium,
color = Black,
fontSize = 16.sp,
modifier = Modifier.padding(
horizontal = 10.dp,
vertical = 20.dp
)
)
if (userData.hasBookings()) {
SortedBookingList(userData = userData)
} else {
EmptyBookings()
}
}
BaseButton(
text = stringResource(R.string.booking_button),
btnColor = Blue,
btnContentColor = White,
onClick = { viewModel.onIntent(MainIntent.Booking) },
modifier = Modifier
.testTag(Main.ADD_BUTTON)
.padding(horizontal = 10.dp, vertical = 15.dp)
.fillMaxWidth(),
)
}
}
}
@Composable
fun SortedBookingList(userData: UserEntity) {
val sortedBookings = remember(userData.booking) {
userData.getSortedBookingsWithFormattedDate()
}
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
) {
itemsIndexed(
items = sortedBookings
) { index, (originalDate, formattedDate, bookingInfo) ->
BookingItem(
originalDate = originalDate,
formattedDate = formattedDate,
bookingInfo = bookingInfo,
index = index
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
@Composable
fun BookingItem(
originalDate: String,
formattedDate: String,
bookingInfo: BookingInfo,
index: Int
) {
Row(
modifier = Modifier
.testTag(Main.getIdItemByPosition(index))
.fillMaxWidth()
.padding(vertical = 20.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
BaseText14(
text = bookingInfo.place,
modifier = Modifier.testTag(Main.ITEM_PLACE)
)
BaseText14(
text = formattedDate,
modifier = Modifier.testTag(Main.ITEM_DATE)
)
}
}
@Composable
fun EmptyBookings() {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
BaseText16(
text = stringResource(R.string.main_empty_booking)
)
}
}

View File

@@ -0,0 +1,9 @@
package ru.myitschool.work.ui.screen.main
import ru.myitschool.work.domain.main.entities.UserEntity
sealed interface MainState {
data class Data(val userData: UserEntity): MainState
object Loading: MainState
object Error: MainState
}

View File

@@ -0,0 +1,82 @@
package ru.myitschool.work.ui.screen.main
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.App
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.main.LoadDataUseCase
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val dataStoreManager by lazy {
(getApplication() as App).dataStoreManager
}
private val loadDataUseCase by lazy { LoadDataUseCase(MainRepository) }
private val _uiState = MutableStateFlow<MainState>(MainState.Loading)
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<MainAction> = MutableSharedFlow()
val actionFlow: SharedFlow<MainAction> = _actionFlow
init {
loadData()
}
private fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
_uiState.update { MainState.Loading }
try {
val userCode = dataStoreManager.getUserCode().first()
if (userCode.code.isEmpty()) {
_actionFlow.emit(MainAction.Auth)
return@launch
}
loadDataUseCase.invoke(userCode.code).fold(
onSuccess = { data ->
_uiState.update { MainState.Data(data) }
},
onFailure = { error ->
error.printStackTrace()
_uiState.update { MainState.Error }
}
)
} catch (error: Exception) {
error.printStackTrace()
_uiState.update { MainState.Error }
}
}
}
fun onIntent( intent: MainIntent) {
when(intent) {
is MainIntent.LoadData -> loadData()
is MainIntent.Booking -> {
viewModelScope.launch(Dispatchers.Default) {
_actionFlow.emit(MainAction.Booking)
}
}
is MainIntent.Logout -> {
viewModelScope.launch(Dispatchers.IO) {
dataStoreManager.clearUserCode()
_actionFlow.emit(MainAction.Auth)
}
}
}
}
}

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,44 @@
package ru.myitschool.work.ui.screen.splash
import android.app.Application
import android.util.Log
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 = if (userCode.code.isEmpty()) false else true
_splashState.value = if (isAuthenticated) {
SplashState.Authenticated
} else {
SplashState.UnAuthenticated
}
} catch (e: Exception) {
_splashState.value = SplashState.Error(e.message ?: "Unknown error")
}
}
}
}

View File

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

View File

@@ -2,19 +2,27 @@ 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 Typography = Typography(
bodySmall = TextStyle(
fontWeight = FontWeight.Medium,
),
bodyMedium = TextStyle(
fontWeight = FontWeight.SemiBold,
),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
fontWeight = FontWeight.Bold,
),
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,

View File

@@ -0,0 +1,26 @@
package ru.myitschool.work
import java.text.SimpleDateFormat
import java.util.Locale
fun String.formatDate(): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
val date = inputFormat.parse(this)
outputFormat.format(date)
} catch (e: Exception) {
this
}
}
fun String.formatBookingDate(): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd.MM", Locale.getDefault())
val date = inputFormat.parse(this)
outputFormat.format(date)
} catch (e: Exception) {
this
}
}

View File

@@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="34dp"
android:height="34dp"
android:viewportWidth="34"
android:viewportHeight="34">
<path
android:pathData="M17.321,18.625C18.08,18.625 18.695,18.01 18.695,17.252C18.695,16.493 18.08,15.878 17.321,15.878C16.563,15.878 15.948,16.493 15.948,17.252C15.948,18.01 16.563,18.625 17.321,18.625Z"
android:strokeLineJoin="round"
android:strokeWidth="3.38015"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
<path
android:pathData="M17.321,9.012C18.08,9.012 18.695,8.398 18.695,7.639C18.695,6.881 18.08,6.266 17.321,6.266C16.563,6.266 15.948,6.881 15.948,7.639C15.948,8.398 16.563,9.012 17.321,9.012Z"
android:strokeLineJoin="round"
android:strokeWidth="3.38015"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
<path
android:pathData="M17.321,28.237C18.08,28.237 18.695,27.622 18.695,26.864C18.695,26.105 18.08,25.491 17.321,25.491C16.563,25.491 15.948,26.105 15.948,26.864C15.948,27.622 16.563,28.237 17.321,28.237Z"
android:strokeLineJoin="round"
android:strokeWidth="3.38015"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="151dp"
android:height="72dp"
android:viewportWidth="151"
android:viewportHeight="72">
<path
android:pathData="M4,8C4,3.58 7.58,0 12,0H139C143.42,0 147,3.58 147,8V56C147,60.42 143.42,64 139,64H12C7.58,64 4,60.42 4,56V8Z"
android:fillColor="#4B70F5"/>
<path
android:pathData="M34.46,40.23C33.51,40.23 32.61,40.16 31.74,40C30.88,39.84 30.1,39.62 29.39,39.32L29.77,36.47C30.63,36.84 31.47,37.12 32.3,37.32C33.13,37.5 33.91,37.6 34.64,37.6C35.58,37.6 36.29,37.43 36.77,37.11C37.25,36.77 37.49,36.28 37.49,35.64C37.49,34.8 37.02,34.17 36.06,33.73L33.47,32.5C32.31,31.95 31.41,31.28 30.78,30.47C30.15,29.67 29.84,28.74 29.84,27.7C29.84,26.24 30.3,25.12 31.22,24.32C32.15,23.52 33.46,23.13 35.16,23.13C36.22,23.13 37.2,23.3 38.09,23.65C38.99,24 39.81,24.52 40.55,25.22L38.62,27.36C38.02,26.82 37.43,26.43 36.86,26.16C36.3,25.89 35.73,25.75 35.17,25.75C34.44,25.75 33.87,25.92 33.46,26.25C33.05,26.58 32.85,27.06 32.85,27.7C32.85,28.13 32.99,28.52 33.26,28.86C33.54,29.18 33.95,29.48 34.47,29.75L36.8,30.91C38,31.5 38.91,32.19 39.54,32.97C40.18,33.74 40.5,34.64 40.5,35.65C40.5,37.14 39.99,38.28 38.96,39.06C37.93,39.84 36.43,40.23 34.46,40.23ZM51.58,40.23C49.54,40.23 48.07,39.82 47.16,38.99C46.25,38.16 45.8,36.84 45.8,35.02V33.2H48.68V34.79C48.68,35.75 48.93,36.45 49.41,36.88C49.9,37.32 50.7,37.54 51.81,37.54C52.18,37.54 52.57,37.52 53,37.48C53.42,37.43 53.9,37.38 54.44,37.3L54.72,39.95C54.2,40.05 53.68,40.12 53.17,40.16C52.67,40.21 52.14,40.23 51.58,40.23ZM45.8,34.02V23.83H48.68V34.02H45.8ZM42.77,30.14V27.58H54.25V30.14H42.77ZM65.55,40.12L65.32,36.44L65.16,35.05V32.31C65.16,31.59 64.88,31.05 64.3,30.72C63.73,30.38 62.88,30.19 61.75,30.16L58.66,30.07L58.89,27.46L61.52,27.5C63.69,27.53 65.3,27.97 66.36,28.83C67.41,29.68 67.94,30.93 67.94,32.57V37.4L69.81,37.66V40L65.55,40.12ZM61.27,40.23C59.96,40.23 58.94,39.91 58.21,39.27C57.49,38.63 57.13,37.73 57.13,36.55C57.13,35.2 57.62,34.15 58.59,33.43C59.55,32.7 60.93,32.34 62.7,32.34C63.4,32.34 64.02,32.38 64.55,32.45C65.08,32.53 65.59,32.65 66.07,32.82L65.56,34.97C65.05,34.86 64.58,34.79 64.14,34.77C63.7,34.75 63.25,34.74 62.77,34.74C60.93,34.74 60,35.28 60,36.37C60,36.84 60.16,37.21 60.48,37.46C60.81,37.71 61.29,37.83 61.9,37.83C62.62,37.83 63.22,37.7 63.71,37.45C64.19,37.18 64.55,36.85 64.8,36.45C65.04,36.04 65.16,35.63 65.16,35.2V34.19L65.72,37.38H64.73L65.14,37C65.12,37.72 64.95,38.32 64.63,38.8C64.32,39.28 63.88,39.64 63.31,39.88C62.74,40.12 62.06,40.23 61.27,40.23ZM76.57,34.66L75.85,30.53H76.88C77.01,28.41 78.1,27.34 80.15,27.34C81.37,27.34 82.28,27.75 82.89,28.56C83.5,29.38 83.8,30.6 83.8,32.24H80.93C80.93,31.44 80.8,30.86 80.52,30.51C80.26,30.16 79.82,29.98 79.22,29.98C78.33,29.98 77.67,30.39 77.23,31.2C76.79,32.01 76.57,33.16 76.57,34.66ZM71.01,40V37.55H79.54V40H71.01ZM73.69,40V27.58H76.11L76.57,31.14V40H73.69ZM71.48,30.03V27.58H75.94L76.18,30.03H71.48ZM93.77,40.23C91.73,40.23 90.25,39.82 89.35,38.99C88.44,38.16 87.99,36.84 87.99,35.02V33.2H90.87V34.79C90.87,35.75 91.11,36.45 91.6,36.88C92.09,37.32 92.89,37.54 94,37.54C94.37,37.54 94.76,37.52 95.18,37.48C95.61,37.43 96.09,37.38 96.63,37.3L96.91,39.95C96.38,40.05 95.87,40.12 95.36,40.16C94.86,40.21 94.33,40.23 93.77,40.23ZM87.99,34.02V23.83H90.87V34.02H87.99ZM84.95,30.14V27.58H96.44V30.14H84.95Z"
android:fillColor="#F5F5F5"/>
<path
android:pathData="M110.33,26L119.67,32L110.33,38V26Z"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#F5F5F5"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="28dp"
android:viewportWidth="16"
android:viewportHeight="28">
<path
android:pathData="M14,26L2,14L14,2"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="10dp"
android:viewportWidth="16"
android:viewportHeight="10">
<path
android:pathData="M2,2L7.931,7.931L13.862,2"
android:strokeLineJoin="round"
android:strokeWidth="2.43316"
android:fillColor="#00000000"
android:strokeColor="#4B70F5"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M18,2V8M30,2V8M18,40V46M30,40V46M40,18H46M40,28H46M2,18H8M2,28H8M12,8H36C38.209,8 40,9.791 40,12V36C40,38.209 38.209,40 36,40H12C9.791,40 8,38.209 8,36V12C8,9.791 9.791,8 12,8ZM18,18H30V30H18V18Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="39dp"
android:viewportWidth="40"
android:viewportHeight="39">
<path
android:pathData="M20,36.243H38M29,3.243C29.796,2.447 30.875,2 32,2C32.557,2 33.109,2.11 33.624,2.323C34.138,2.536 34.606,2.849 35,3.243C35.394,3.637 35.707,4.104 35.92,4.619C36.133,5.134 36.243,5.685 36.243,6.243C36.243,6.8 36.133,7.351 35.92,7.866C35.707,8.381 35.394,8.849 35,9.243L10,34.243L2,36.243L4,28.243L29,3.243Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="44dp"
android:viewportWidth="36"
android:viewportHeight="44">
<path
android:pathData="M20,2H6C4.939,2 3.922,2.421 3.172,3.172C2.421,3.922 2,4.939 2,6V38C2,39.061 2.421,40.078 3.172,40.828C3.922,41.579 4.939,42 6,42H30C31.061,42 32.078,41.579 32.828,40.828C33.579,40.078 34,39.061 34,38V16M20,2L34,16M20,2V16H34"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="44dp"
android:viewportWidth="36"
android:viewportHeight="44">
<path
android:pathData="M22,2H6C4.939,2 3.922,2.421 3.172,3.172C2.421,3.922 2,4.939 2,6V38C2,39.061 2.421,40.078 3.172,40.828C3.922,41.579 4.939,42 6,42H30C31.061,42 32.078,41.579 32.828,40.828C33.579,40.078 34,39.061 34,38V14M22,2L34,14M22,2V14H34M18,34V22M12,28H24"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="40dp"
android:viewportWidth="44"
android:viewportHeight="40">
<path
android:pathData="M42,34C42,35.061 41.579,36.078 40.828,36.828C40.078,37.579 39.061,38 38,38H6C4.939,38 3.922,37.579 3.172,36.828C2.421,36.078 2,35.061 2,34V6C2,4.939 2.421,3.922 3.172,3.172C3.922,2.421 4.939,2 6,2H16L20,8H38C39.061,8 40.078,8.421 40.828,9.172C41.579,9.922 42,10.939 42,12V34Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29dp"
android:height="23dp"
android:viewportWidth="29"
android:viewportHeight="23">
<path
android:pathData="M3.602,22.236C2.847,22.236 2.2,21.967 1.663,21.429C1.125,20.891 0.856,20.245 0.856,19.489V3.012C0.856,2.257 1.125,1.61 1.663,1.072C2.2,0.535 2.847,0.266 3.602,0.266H11.841L14.587,3.012H25.572C26.327,3.012 26.974,3.281 27.512,3.819C28.05,4.356 28.319,5.003 28.319,5.758V19.489C28.319,20.245 28.05,20.891 27.512,21.429C26.974,21.967 26.327,22.236 25.572,22.236H3.602Z"
android:fillColor="#3DC2EC"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="40dp"
android:viewportWidth="44"
android:viewportHeight="40">
<path
android:pathData="M22,18V30M16,24H28M42,34C42,35.061 41.579,36.078 40.828,36.828C40.078,37.579 39.061,38 38,38H6C4.939,38 3.922,37.579 3.172,36.828C2.421,36.078 2,35.061 2,34V6C2,4.939 2.421,3.922 3.172,3.172C3.922,2.421 4.939,2 6,2H16L20,8H38C39.061,8 40.078,8.421 40.828,9.172C41.579,9.922 42,10.939 42,12V34Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="20dp"
android:viewportWidth="48"
android:viewportHeight="20">
<path
android:pathData="M2,10H13.9M33.92,10H45.82M31.9,10C31.9,14.418 28.318,18 23.9,18C19.482,18 15.9,14.418 15.9,10C15.9,5.582 19.482,2 23.9,2C28.318,2 31.9,5.582 31.9,10Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="47dp"
android:height="47dp"
android:viewportWidth="47"
android:viewportHeight="47">
<path
android:pathData="M23.5,0.313C22.665,0.313 21.85,0.617 21.223,1.244L16.564,5.954C16.312,6.09 16.099,6.284 15.943,6.523L1.244,21.223C-0.011,22.471 -0.011,24.522 1.244,25.777L21.223,45.756C22.478,47.005 24.522,47.005 25.777,45.756L45.756,25.777C47.011,24.529 47.011,22.478 45.756,21.223L25.777,1.244C25.15,0.617 24.334,0.313 23.5,0.313ZM23.5,3.677L43.323,23.5L23.5,43.323L3.677,23.5L17.392,9.784L20.291,12.683C20.213,12.961 20.187,13.258 20.187,13.563C20.187,14.785 20.854,15.833 21.844,16.409V30.591C20.854,31.167 20.187,32.215 20.187,33.438C20.187,35.268 21.669,36.75 23.5,36.75C25.331,36.75 26.812,35.268 26.812,33.438C26.812,32.215 26.146,31.167 25.156,30.591V17.6L30.229,22.672C30.157,22.937 30.125,23.215 30.125,23.5C30.125,25.331 31.607,26.813 33.437,26.813C35.268,26.813 36.75,25.331 36.75,23.5C36.75,21.669 35.268,20.188 33.437,20.188C33.153,20.188 32.875,20.22 32.609,20.291L26.709,14.391C26.78,14.125 26.812,13.847 26.812,13.563C26.812,11.732 25.331,10.25 23.5,10.25C23.196,10.25 22.898,10.276 22.62,10.354L19.722,7.455L23.5,3.677Z"
android:fillColor="#3DC2EC"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M32,26C28.686,26 26,28.686 26,32C26,35.314 28.686,38 32,38C35.314,38 38,35.314 38,32C38,28.686 35.314,26 32,26ZM32,26V12C32,10.939 31.579,9.922 30.828,9.172C30.078,8.421 29.061,8 28,8H22M8,14C11.314,14 14,11.314 14,8C14,4.686 11.314,2 8,2C4.686,2 2,4.686 2,8C2,11.314 4.686,14 8,14ZM8,14V38"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="43dp"
android:height="47dp"
android:viewportWidth="43"
android:viewportHeight="47">
<path
android:pathData="M16,38.054C6,41.054 6,33.054 2,32.054M30,44.054V36.314C30.075,35.36 29.946,34.401 29.622,33.501C29.298,32.601 28.786,31.78 28.12,31.094C34.4,30.394 41,28.014 41,17.094C41,14.301 39.925,11.616 38,9.594C38.912,7.151 38.847,4.45 37.82,2.054C37.82,2.054 35.46,1.354 30,5.014C25.416,3.771 20.584,3.771 16,5.014C10.54,1.354 8.18,2.054 8.18,2.054C7.153,4.45 7.088,7.151 8,9.594C6.06,11.631 4.985,14.34 5,17.154C5,27.994 11.6,30.374 17.88,31.154C17.222,31.833 16.715,32.644 16.391,33.533C16.067,34.423 15.934,35.37 16,36.314V44.054"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="16dp"
android:viewportWidth="10"
android:viewportHeight="16">
<path
android:pathData="M2,13.862L7.931,7.931L2,2"
android:strokeLineJoin="round"
android:strokeWidth="2.43316"
android:fillColor="#00000000"
android:strokeColor="#4B70F5"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M10,24H38M38,24L24,10M38,24L24,38"
android:strokeLineJoin="round"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,13H17V11H7V13ZM5,21C4.45,21 3.979,20.804 3.588,20.413C3.196,20.021 3,19.55 3,19V5C3,4.45 3.196,3.979 3.588,3.588C3.979,3.196 4.45,3 5,3H19C19.55,3 20.021,3.196 20.413,3.588C20.804,3.979 21,4.45 21,5V19C21,19.55 20.804,20.021 20.413,20.413C20.021,20.804 19.55,21 19,21H5Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M10.6,16.2L17.65,9.15L16.25,7.75L10.6,13.4L7.75,10.55L6.35,11.95L10.6,16.2ZM5,21C4.45,21 3.979,20.804 3.588,20.413C3.196,20.021 3,19.55 3,19V5C3,4.45 3.196,3.979 3.588,3.588C3.979,3.196 4.45,3 5,3H19C19.55,3 20.021,3.196 20.413,3.588C20.804,3.979 21,4.45 21,5V19C21,19.55 20.804,20.021 20.413,20.413C20.021,20.804 19.55,21 19,21H5Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="22dp"
android:viewportWidth="18"
android:viewportHeight="22">
<path
android:pathData="M9.875,2H3.75C3.286,2 2.841,2.184 2.513,2.513C2.184,2.841 2,3.286 2,3.75V17.75C2,18.214 2.184,18.659 2.513,18.987C2.841,19.316 3.286,19.5 3.75,19.5H14.25C14.714,19.5 15.159,19.316 15.487,18.987C15.816,18.659 16,18.214 16,17.75V8.125L9.875,2Z"
android:fillColor="#4B70F5"/>
<path
android:pathData="M9.875,2V8.125H16"
android:fillColor="#4B70F5"/>
<group>
<clip-path
android:pathData="M0,0h18v22h-18zM9.875,2H3.75C3.286,2 2.841,2.184 2.513,2.513C2.184,2.841 2,3.286 2,3.75V17.75C2,18.214 2.184,18.659 2.513,18.987C2.841,19.316 3.286,19.5 3.75,19.5H14.25C14.714,19.5 15.159,19.316 15.487,18.987C15.816,18.659 16,18.214 16,17.75V8.125L9.875,2ZM9.875,2V8.125H16"/>
<path
android:pathData="M9.875,2L11.112,0.763C10.784,0.434 10.339,0.25 9.875,0.25V2ZM3.75,2L3.75,0.25L3.75,2ZM2,3.75H0.25H2ZM2,17.75L0.25,17.75L2,17.75ZM16,8.125H17.75C17.75,7.661 17.566,7.216 17.237,6.888L16,8.125ZM9.875,8.125H8.125C8.125,9.092 8.908,9.875 9.875,9.875V8.125ZM9.875,0.25H3.75V3.75H9.875V0.25ZM3.75,0.25C2.822,0.25 1.931,0.619 1.275,1.275L3.75,3.75L3.75,3.75L3.75,0.25ZM1.275,1.275C0.619,1.931 0.25,2.822 0.25,3.75L3.75,3.75L3.75,3.75L1.275,1.275ZM0.25,3.75V17.75H3.75V3.75H0.25ZM0.25,17.75C0.25,18.678 0.619,19.569 1.275,20.225L3.75,17.75L3.75,17.75L0.25,17.75ZM1.275,20.225C1.931,20.881 2.822,21.25 3.75,21.25V17.75L3.75,17.75L1.275,20.225ZM3.75,21.25H14.25V17.75H3.75V21.25ZM14.25,21.25C15.178,21.25 16.069,20.881 16.725,20.225L14.25,17.75L14.25,17.75V21.25ZM16.725,20.225C17.381,19.569 17.75,18.678 17.75,17.75H14.25L14.25,17.75L16.725,20.225ZM17.75,17.75V8.125H14.25V17.75H17.75ZM17.237,6.888L11.112,0.763L8.638,3.237L14.763,9.362L17.237,6.888ZM8.125,2V8.125H11.625V2H8.125ZM9.875,9.875H16V6.375H9.875V9.875Z"
android:fillColor="#4B70F5"/>
</group>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="19dp"
android:height="17dp"
android:viewportWidth="19"
android:viewportHeight="17">
<path
android:pathData="M1.87,16.542C1.356,16.542 0.916,16.343 0.549,15.945C0.183,15.548 0,15.07 0,14.512V2.329C0,1.771 0.183,1.293 0.549,0.895C0.916,0.498 1.356,0.299 1.87,0.299H7.48L9.35,2.329H16.831C17.345,2.329 17.785,2.528 18.152,2.926C18.518,3.323 18.701,3.801 18.701,4.36V14.512C18.701,15.07 18.518,15.548 18.152,15.945C17.785,16.343 17.345,16.542 16.831,16.542H1.87Z"
android:fillColor="#3DC2EC"/>
</vector>

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="27dp"
android:height="28dp"
android:viewportWidth="27"
android:viewportHeight="28">
<path
android:pathData="M9.974,23.29C3.564,25.145 3.564,20.199 1,19.581ZM18.949,27V22.215C18.997,21.625 18.914,21.032 18.706,20.476C18.499,19.919 18.17,19.412 17.744,18.987C21.769,18.555 26,17.083 26,10.332C26,8.605 25.311,6.945 24.077,5.695C24.661,4.184 24.62,2.515 23.962,1.033C23.962,1.033 22.449,0.6 18.949,2.863C16.01,2.095 12.913,2.095 9.974,2.863C6.474,0.6 4.962,1.033 4.962,1.033C4.303,2.515 4.262,4.184 4.846,5.695C3.603,6.954 2.913,8.63 2.923,10.369C2.923,17.071 7.154,18.542 11.179,19.024C10.758,19.445 10.432,19.946 10.225,20.496C10.017,21.045 9.932,21.631 9.974,22.215V27"
android:fillColor="#3DC2EC"/>
<path
android:pathData="M9.974,23.29C3.564,25.145 3.564,20.199 1,19.581M18.949,27V22.215C18.997,21.625 18.914,21.032 18.706,20.476C18.499,19.919 18.17,19.412 17.744,18.987C21.769,18.555 26,17.083 26,10.332C26,8.605 25.311,6.945 24.077,5.695C24.661,4.184 24.62,2.515 23.962,1.033C23.962,1.033 22.449,0.6 18.949,2.863C16.01,2.095 12.913,2.095 9.974,2.863C6.474,0.6 4.962,1.033 4.962,1.033C4.303,2.515 4.262,4.184 4.846,5.695C3.603,6.954 2.913,8.63 2.923,10.369C2.923,17.071 7.154,18.542 11.179,19.024C10.758,19.445 10.432,19.946 10.225,20.496C10.017,21.045 9.932,21.631 9.974,22.215V27"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M28,4H12C10.939,4 9.922,4.421 9.172,5.172C8.421,5.922 8,6.939 8,8V40C8,41.061 8.421,42.078 9.172,42.828C9.922,43.579 10.939,44 12,44H36C37.061,44 38.078,43.579 38.828,42.828C39.579,42.078 40,41.061 40,40V16M28,4L40,16M28,4V16H40M32,26H16M32,34H16M20,18H16"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="24dp"
android:viewportWidth="20"
android:viewportHeight="24">
<path
android:pathData="M11,2H4C3.47,2 2.961,2.211 2.586,2.586C2.211,2.961 2,3.47 2,4V20C2,20.53 2.211,21.039 2.586,21.414C2.961,21.789 3.47,22 4,22H16C16.53,22 17.039,21.789 17.414,21.414C17.789,21.039 18,20.53 18,20V9L11,2Z"
android:fillColor="#3DC2EC"/>
<path
android:pathData="M11,2V9H18"
android:fillColor="#3DC2EC"/>
<group>
<clip-path
android:pathData="M0,0h20v24h-20zM11,2H4C3.47,2 2.961,2.211 2.586,2.586C2.211,2.961 2,3.47 2,4V20C2,20.53 2.211,21.039 2.586,21.414C2.961,21.789 3.47,22 4,22H16C16.53,22 17.039,21.789 17.414,21.414C17.789,21.039 18,20.53 18,20V9L11,2ZM11,2V9H18"/>
<path
android:pathData="M11,2L12.237,0.763C11.909,0.434 11.464,0.25 11,0.25V2ZM4,2L4,0.25L4,2ZM2,4H0.25H2ZM2,20L0.25,20L2,20ZM18,9H19.75C19.75,8.536 19.566,8.091 19.237,7.763L18,9ZM11,9H9.25C9.25,9.967 10.033,10.75 11,10.75V9ZM11,2V0.25H4V2V3.75H11V2ZM4,2L4,0.25C3.005,0.25 2.052,0.645 1.348,1.348L2.586,2.586L3.823,3.823C3.87,3.776 3.934,3.75 4,3.75L4,2ZM2.586,2.586L1.348,1.348C0.645,2.052 0.25,3.005 0.25,4L2,4L3.75,4C3.75,3.934 3.776,3.87 3.823,3.823L2.586,2.586ZM2,4H0.25V20H2H3.75V4H2ZM2,20L0.25,20C0.25,20.995 0.645,21.948 1.348,22.652L2.586,21.414L3.823,20.177C3.776,20.13 3.75,20.066 3.75,20L2,20ZM2.586,21.414L1.348,22.652C2.052,23.355 3.005,23.75 4,23.75V22V20.25C3.934,20.25 3.87,20.224 3.823,20.177L2.586,21.414ZM4,22V23.75H16V22V20.25H4V22ZM16,22V23.75C16.995,23.75 17.948,23.355 18.652,22.652L17.414,21.414L16.177,20.177C16.13,20.224 16.066,20.25 16,20.25V22ZM17.414,21.414L18.652,22.652C19.355,21.948 19.75,20.995 19.75,20H18H16.25C16.25,20.066 16.224,20.13 16.177,20.177L17.414,21.414ZM18,20H19.75V9H18H16.25V20H18ZM18,9L19.237,7.763L12.237,0.763L11,2L9.763,3.237L16.763,10.237L18,9ZM11,2H9.25V9H11H12.75V2H11ZM11,9V10.75H18V9V7.25H11V9Z"
android:fillColor="#3DC2EC"/>
</group>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="33dp"
android:height="29dp"
android:viewportWidth="33"
android:viewportHeight="29">
<path
android:pathData="M31.602,3.265V11.504M31.602,11.504H23.363M31.602,11.504L25.23,5.517C23.754,4.041 21.929,2.962 19.923,2.382C17.918,1.802 15.798,1.74 13.762,2.2C11.726,2.661 9.84,3.63 8.28,5.017C6.719,6.404 5.536,8.163 4.839,10.131M1.393,25.235V16.997M1.393,16.997H9.632M1.393,16.997L7.764,22.983C9.24,24.46 11.066,25.539 13.071,26.119C15.076,26.699 17.196,26.761 19.232,26.301C21.268,25.84 23.154,24.871 24.715,23.484C26.275,22.097 27.459,20.338 28.155,18.37"
android:strokeLineJoin="round"
android:strokeWidth="3.38"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<group>
<clip-path
android:pathData="M0,0h48v48h-48z"/>
<path
android:pathData="M2,8V20M2,20H14M2,20L11.28,11.28C14.042,8.523 17.625,6.738 21.489,6.194C25.354,5.65 29.29,6.377 32.706,8.265C36.121,10.153 38.83,13.1 40.425,16.662C42.02,20.224 42.414,24.207 41.548,28.013C40.682,31.818 38.603,35.239 35.624,37.759C32.645,40.28 28.927,41.765 25.031,41.989C21.135,42.213 17.272,41.165 14.023,39.003C10.775,36.84 8.317,33.681 7.02,30"
android:strokeLineJoin="round"
android:strokeWidth="6"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="16dp"
android:viewportWidth="12"
android:viewportHeight="16">
<path
android:pathData="M1.334,2L10.667,8L1.334,14V2Z"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
android:fillColor="#00000000"
android:strokeColor="#F5F5F5"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="48dp"
android:viewportWidth="52"
android:viewportHeight="48">
<path
android:pathData="M27.152,2.727L33.286,16.525L48.853,17.863C49.098,17.882 49.333,17.971 49.526,18.119C49.72,18.266 49.863,18.465 49.939,18.691C50.015,18.917 50.02,19.16 49.953,19.389C49.886,19.617 49.751,19.821 49.563,19.976L37.78,29.864L41.255,44.526C41.291,44.68 41.295,44.839 41.267,44.995C41.239,45.151 41.18,45.3 41.092,45.433C41.004,45.567 40.89,45.682 40.756,45.773C40.621,45.863 40.47,45.928 40.31,45.962C39.979,46.029 39.633,45.967 39.349,45.789L25.977,38.104L12.569,45.827C12.429,45.909 12.274,45.963 12.112,45.986C11.95,46.01 11.784,46.003 11.625,45.965C11.466,45.926 11.317,45.859 11.185,45.764C11.054,45.671 10.943,45.552 10.859,45.417C10.775,45.284 10.719,45.137 10.694,44.983C10.669,44.83 10.676,44.673 10.715,44.522L14.193,29.86L2.425,19.976C2.178,19.765 2.027,19.468 2.003,19.151C1.98,18.833 2.087,18.519 2.301,18.277C2.536,18.044 2.857,17.909 3.195,17.901L18.703,16.562L24.837,2.727C24.936,2.511 25.098,2.327 25.303,2.198C25.509,2.069 25.749,2 25.994,2C26.24,2 26.48,2.069 26.685,2.198C26.891,2.327 27.053,2.511 27.152,2.727Z"
android:strokeLineJoin="bevel"
android:strokeWidth="4"
android:fillColor="#FFD401"
android:strokeColor="#FFD401"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="44dp"
android:viewportWidth="40"
android:viewportHeight="44">
<path
android:pathData="M2,10H6M6,10H38M6,10V38C6,39.061 6.421,40.078 7.172,40.828C7.922,41.579 8.939,42 10,42H30C31.061,42 32.078,41.579 32.828,40.828C33.579,40.078 34,39.061 34,38V10M12,10V6C12,4.939 12.421,3.922 13.172,3.172C13.922,2.421 14.939,2 16,2H24C25.061,2 26.078,2.421 26.828,3.172C27.579,3.922 28,4.939 28,6V10"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.2,0.788L44.155,0.788A10.985,10.985 0,0 1,55.14 11.773L55.14,44.728A10.985,10.985 0,0 1,44.155 55.713L11.2,55.713A10.985,10.985 0,0 1,0.215 44.728L0.215,11.773A10.985,10.985 0,0 1,11.2 0.788z"/>
<path
android:pathData="M11.2,0.788L44.155,0.788A10.985,10.985 0,0 1,55.14 11.773L55.14,44.728A10.985,10.985 0,0 1,44.155 55.713L11.2,55.713A10.985,10.985 0,0 1,0.215 44.728L0.215,11.773A10.985,10.985 0,0 1,11.2 0.788z"
android:fillColor="#2D246D"/>
<path
android:pathData="M31.797,36.489L23.558,28.25L31.797,20.012"
android:strokeLineJoin="round"
android:strokeWidth="3.38"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.11,0.788L44.065,0.788A10.985,10.985 0,0 1,55.05 11.773L55.05,44.728A10.985,10.985 0,0 1,44.065 55.713L11.11,55.713A10.985,10.985 0,0 1,0.125 44.728L0.125,11.773A10.985,10.985 0,0 1,11.11 0.788z"/>
<path
android:pathData="M11.11,0.788L44.065,0.788A10.985,10.985 0,0 1,55.05 11.773L55.05,44.728A10.985,10.985 0,0 1,44.065 55.713L11.11,55.713A10.985,10.985 0,0 1,0.125 44.728L0.125,11.773A10.985,10.985 0,0 1,11.11 0.788z"
android:fillColor="#2D246D"/>
<path
android:pathData="M16.602,39.236C15.847,39.236 15.2,38.967 14.663,38.429C14.125,37.891 13.856,37.245 13.856,36.489V20.012C13.856,19.257 14.125,18.61 14.663,18.072C15.2,17.535 15.847,17.266 16.602,17.266H24.841L27.587,20.012H38.572C39.327,20.012 39.974,20.281 40.512,20.819C41.05,21.356 41.319,22.003 41.319,22.758V36.489C41.319,37.245 41.05,37.891 40.512,38.429C39.974,38.967 39.327,39.236 38.572,39.236H16.602Z"
android:fillColor="#3DC2EC"/>
</group>
</vector>

View File

@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.93,0.788L44.887,0.788A10.986,10.986 0,0 1,55.872 11.774L55.872,44.73A10.986,10.986 0,0 1,44.887 55.715L11.93,55.715A10.986,10.986 0,0 1,0.945 44.73L0.945,11.774A10.986,10.986 0,0 1,11.93 0.788z"/>
<path
android:pathData="M11.93,0.788L44.887,0.788A10.986,10.986 0,0 1,55.872 11.774L55.872,44.73A10.986,10.986 0,0 1,44.887 55.715L11.93,55.715A10.986,10.986 0,0 1,0.945 44.73L0.945,11.774A10.986,10.986 0,0 1,11.93 0.788z"
android:fillColor="#2D246D"/>
<group>
<clip-path
android:pathData="M11.93,11.773h32.956v32.956h-32.956z"/>
<path
android:pathData="M24.289,37.864C17.423,39.924 17.423,34.431 14.677,33.744M33.901,41.984V36.669C33.953,36.014 33.864,35.356 33.642,34.738C33.419,34.12 33.068,33.557 32.611,33.085C36.922,32.605 41.454,30.971 41.454,23.473C41.453,21.556 40.716,19.712 39.394,18.324C40.02,16.646 39.976,14.792 39.271,13.147C39.271,13.147 37.65,12.666 33.901,15.179C30.754,14.326 27.436,14.326 24.289,15.179C20.54,12.666 18.92,13.147 18.92,13.147C18.215,14.792 18.17,16.646 18.796,18.324C17.465,19.722 16.726,21.583 16.736,23.514C16.736,30.957 21.268,32.591 25.58,33.126C25.128,33.593 24.78,34.15 24.557,34.761C24.335,35.371 24.243,36.021 24.289,36.669V41.984"
android:strokeLineJoin="round"
android:strokeWidth="3.38015"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</group>
</group>
</vector>

View File

@@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.843,0.788L44.8,0.788A10.986,10.986 0,0 1,55.785 11.774L55.785,44.73A10.986,10.986 0,0 1,44.8 55.715L11.843,55.715A10.986,10.986 0,0 1,0.858 44.73L0.858,11.774A10.986,10.986 0,0 1,11.843 0.788z"/>
<path
android:pathData="M11.843,0.788L44.8,0.788A10.986,10.986 0,0 1,55.785 11.774L55.785,44.73A10.986,10.986 0,0 1,44.8 55.715L11.843,55.715A10.986,10.986 0,0 1,0.858 44.73L0.858,11.774A10.986,10.986 0,0 1,11.843 0.788z"
android:fillColor="#2D246D"/>
<path
android:pathData="M28.322,39.237C27.566,39.237 26.92,38.968 26.382,38.43C25.844,37.893 25.575,37.246 25.575,36.491C25.575,35.735 25.844,35.089 26.382,34.551C26.92,34.013 27.566,33.744 28.322,33.744C29.077,33.744 29.723,34.013 30.261,34.551C30.799,35.089 31.068,35.735 31.068,36.491C31.068,37.246 30.799,37.893 30.261,38.43C29.723,38.968 29.077,39.237 28.322,39.237ZM28.322,30.998C27.566,30.998 26.92,30.729 26.382,30.191C25.844,29.653 25.575,29.007 25.575,28.252C25.575,27.496 25.844,26.85 26.382,26.312C26.92,25.774 27.566,25.505 28.322,25.505C29.077,25.505 29.723,25.774 30.261,26.312C30.799,26.85 31.068,27.496 31.068,28.252C31.068,29.007 30.799,29.653 30.261,30.191C29.723,30.729 29.077,30.998 28.322,30.998ZM28.322,22.759C27.566,22.759 26.92,22.49 26.382,21.952C25.844,21.414 25.575,20.768 25.575,20.013C25.575,19.257 25.844,18.611 26.382,18.073C26.92,17.535 27.566,17.266 28.322,17.266C29.077,17.266 29.723,17.535 30.261,18.073C30.799,18.611 31.068,19.257 31.068,20.013C31.068,20.768 30.799,21.414 30.261,21.952C29.723,22.49 29.077,22.759 28.322,22.759Z"
android:fillColor="#3DC2EC"/>
</group>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="55dp"
android:height="56dp"
android:viewportWidth="55"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.02,0.788L43.975,0.788A10.985,10.985 0,0 1,54.96 11.773L54.96,44.728A10.985,10.985 0,0 1,43.975 55.713L11.02,55.713A10.985,10.985 0,0 1,0.035 44.728L0.035,11.773A10.985,10.985 0,0 1,11.02 0.788z"/>
<path
android:pathData="M11.02,0.788L43.975,0.788A10.985,10.985 0,0 1,54.96 11.773L54.96,44.728A10.985,10.985 0,0 1,43.975 55.713L11.02,55.713A10.985,10.985 0,0 1,0.035 44.728L0.035,11.773A10.985,10.985 0,0 1,11.02 0.788z"
android:fillColor="#2D246D"/>
<path
android:pathData="M17.885,15.892L37.109,28.25L17.885,40.608V15.892Z"
android:strokeLineJoin="round"
android:strokeWidth="3.38"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<group>
<clip-path
android:pathData="M11.843,0.788L44.8,0.788A10.986,10.986 0,0 1,55.785 11.774L55.785,44.73A10.986,10.986 0,0 1,44.8 55.715L11.843,55.715A10.986,10.986 0,0 1,0.858 44.73L0.858,11.774A10.986,10.986 0,0 1,11.843 0.788z"/>
<path
android:pathData="M11.843,0.788L44.8,0.788A10.986,10.986 0,0 1,55.785 11.774L55.785,44.73A10.986,10.986 0,0 1,44.8 55.715L11.843,55.715A10.986,10.986 0,0 1,0.858 44.73L0.858,11.774A10.986,10.986 0,0 1,11.843 0.788z"
android:fillColor="#2D246D"/>
<path
android:pathData="M35.188,40.61V29.625H21.456V40.61M21.456,15.893V22.759H32.441M37.934,40.61H18.709C17.981,40.61 17.282,40.321 16.767,39.806C16.252,39.291 15.963,38.592 15.963,37.864V18.639C15.963,17.911 16.252,17.212 16.767,16.698C17.282,16.182 17.981,15.893 18.709,15.893H33.814L40.68,22.759V37.864C40.68,38.592 40.391,39.291 39.876,39.806C39.361,40.321 38.662,40.61 37.934,40.61Z"
android:strokeLineJoin="round"
android:strokeWidth="3.38015"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="23dp"
android:height="28dp"
android:viewportWidth="23"
android:viewportHeight="28">
<path
android:pathData="M3.042,3.5L19.958,14L3.042,24.5V3.5Z"
android:strokeLineJoin="round"
android:strokeWidth="6"
android:fillColor="#3DC2EC"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M38,38L29.3,29.3M34,18C34,26.837 26.837,34 18,34C9.163,34 2,26.837 2,18C2,9.163 9.163,2 18,2C26.837,2 34,9.163 34,18Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M38,38L29.3,29.3M34,18C34,26.837 26.837,34 18,34C9.163,34 2,26.837 2,18C2,9.163 9.163,2 18,2C26.837,2 34,9.163 34,18Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="42dp"
android:viewportWidth="42"
android:viewportHeight="42">
<path
android:pathData="M25.403,10.607C25.036,10.981 24.831,11.484 24.831,12.007C24.831,12.531 25.036,13.033 25.403,13.407L28.603,16.607C28.976,16.974 29.479,17.179 30.003,17.179C30.526,17.179 31.029,16.974 31.403,16.607L38.943,9.067C39.948,11.29 40.253,13.766 39.816,16.166C39.378,18.565 38.22,20.775 36.495,22.5C34.77,24.225 32.561,25.383 30.161,25.82C27.761,26.257 25.285,25.953 23.063,24.947L9.243,38.767C8.447,39.563 7.368,40.01 6.243,40.01C5.117,40.01 4.038,39.563 3.243,38.767C2.447,37.972 2,36.893 2,35.767C2,34.642 2.447,33.563 3.243,32.767L17.063,18.947C16.057,16.725 15.752,14.249 16.19,11.849C16.627,9.449 17.785,7.24 19.51,5.515C21.235,3.79 23.444,2.632 25.844,2.194C28.244,1.757 30.72,2.062 32.943,3.067L25.403,10.607Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="31dp"
android:height="31dp"
android:viewportWidth="31"
android:viewportHeight="31">
<path
android:pathData="M18.067,7.909C17.816,8.166 17.675,8.511 17.675,8.871C17.675,9.23 17.816,9.575 18.067,9.832L20.264,12.029C20.521,12.28 20.866,12.421 21.226,12.421C21.585,12.421 21.93,12.28 22.187,12.029L27.363,6.852C28.054,8.378 28.263,10.078 27.963,11.726C27.663,13.373 26.867,14.89 25.683,16.074C24.499,17.259 22.982,18.054 21.334,18.354C19.687,18.654 17.987,18.445 16.461,17.755L6.973,27.243C6.426,27.789 5.685,28.096 4.913,28.096C4.14,28.096 3.399,27.789 2.853,27.243C2.307,26.697 2,25.956 2,25.183C2,24.411 2.307,23.67 2.853,23.124L12.341,13.635C11.651,12.11 11.442,10.41 11.742,8.762C12.042,7.114 12.837,5.597 14.022,4.413C15.206,3.229 16.723,2.434 18.371,2.133C20.018,1.833 21.718,2.042 23.244,2.733L18.067,7.909Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="44dp"
android:viewportWidth="40"
android:viewportHeight="44">
<path
android:pathData="M2,10H6M6,10H38M6,10V38C6,39.061 6.421,40.078 7.172,40.828C7.922,41.579 8.939,42 10,42H30C31.061,42 32.078,41.579 32.828,40.828C33.579,40.078 34,39.061 34,38V10M12,10V6C12,4.939 12.421,3.922 13.172,3.172C13.922,2.421 14.939,2 16,2H24C25.061,2 26.078,2.421 26.828,3.172C27.579,3.922 28,4.939 28,6V10"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="48dp"
android:viewportWidth="32"
android:viewportHeight="48">
<path
android:pathData="M8.42,27.78L6,46L16,40L26,46L23.58,27.76M30,16C30,23.732 23.732,30 16,30C8.268,30 2,23.732 2,16C2,8.268 8.268,2 16,2C23.732,2 30,8.268 30,16Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="35dp"
android:viewportWidth="24"
android:viewportHeight="35">
<path
android:pathData="M7.156,19.7L5.494,32.209L12.36,28.089L19.226,32.209L17.564,19.686M21.972,11.612C21.972,16.92 17.668,21.224 12.36,21.224C7.051,21.224 2.748,16.92 2.748,11.612C2.748,6.303 7.051,2 12.36,2C17.668,2 21.972,6.303 21.972,11.612Z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M12,22L2,12M2,12L12,2M2,12L26,12C28.122,12 30.157,12.843 31.657,14.343C33.157,15.843 34,17.878 34,20L34,34"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#3DC2EC"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:pathData="M27,3L3,27M3,3L27,27"
android:strokeLineJoin="round"
android:strokeWidth="6"
android:fillColor="#00000000"
android:strokeColor="#CC425E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -1,7 +1,21 @@
<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>
<string name="main_update">Обновить</string>
<string name="main_log_out">Выйти</string>
<string name="main_avatar_description">Фото пользователя</string>
<string name="main_booking_title">Ваши забронированные места</string>
<string name="booking_button">Бронировать</string>
<string name="add_icon_description">Иконка добавления</string>
<string name="data_error_message">Ошибка загрузки данных</string>
<string name="main_empty_booking">Нет бронирований</string>
<string name="book_new_book">Новая встреча</string>
<string name="book_back">Назад</string>
<string name="book_available_date">Доступные даты</string>
<string name="book_choose_place">Выберите место встречи</string>
<string name="book_all_booked">Всё забронировано</string>
<string name="book_error">Ошибка сервера</string>
</resources>