Compare commits

...

11 Commits

Author SHA1 Message Date
solovushka56
c6418793cb fix revert 2025-12-12 12:59:43 +03:00
solovushka56
2da3b58773 Revert "fix"
This reverts commit a04fb915ae.
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
67 changed files with 2098 additions and 89 deletions

View File

@@ -2,14 +2,24 @@ package ru.myitschool.work
import android.app.Application
import android.content.Context
import ru.myitschool.work.data.repo.AuthRepository
class App: Application() {
override fun onCreate() {
super.onCreate()
context = this
}
class App : Application() {
companion object {
lateinit var context: Context
@Volatile
private var instance: App? = null
fun getAppContext(): Context {
return instance?.applicationContext ?: throw IllegalStateException(
"app not initialized")
}
}
override fun onCreate() {
super.onCreate()
instance = this
AuthRepository.getInstance(applicationContext)
}
}

View File

@@ -1,7 +1,10 @@
package ru.myitschool.work.core
object Constants {
const val HOST = "http://10.0.2.2:8080"
// const val HOST = "http://127.0.0.1:8080"
const val AUTH_URL = "/auth"
const val INFO_URL = "/info"
const val BOOKING_URL = "/booking"

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.data.dtos
class BookingDto {
}

View File

@@ -0,0 +1,13 @@
package ru.myitschool.work.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
typealias BookingInfoResponse = Map<String, List<PlaceInfo>>
@Serializable
data class PlaceInfo(
val id: Int,
val place: String
)

View File

@@ -0,0 +1,41 @@
package ru.myitschool.work.data.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class UserInfoResponse(
@SerialName("name")
val name: String,
@SerialName("photoUrl")
val photoUrl: String?,
@SerialName("booking")
val bookingMap: Map<String, BookingInfo> = emptyMap()
) {
val bookings: List<BookingResponse>
get() = bookingMap.map { (date, info) ->
BookingResponse(
date = date,
place = info.place,
bookingId = info.id
)
}
}
@Serializable
data class BookingInfo(
@SerialName("id")
val id: Int,
@SerialName("place")
val place: String
)
@Serializable
data class BookingResponse(
val date: String,
val place: String,
val bookingId: Int
)

View File

@@ -1,16 +1,94 @@
package ru.myitschool.work.data.repo
import android.content.Context
import android.content.Context.MODE_PRIVATE
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import ru.myitschool.work.data.source.NetworkDataSource
object AuthRepository {
class AuthRepository private constructor(context: Context) {
companion object {
@Volatile
private var INSTANCE: AuthRepository? = null
fun getInstance(context: Context): AuthRepository {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: AuthRepository(context.applicationContext).also { INSTANCE = it }
}
}
fun clearInstance() {
INSTANCE = null
}
}
private val PREFS_NAME = "auth_prefs"
private val KEY_CODE = "auth_code"
private val KEY_NAME = "user_name"
private val KEY_PHOTO = "user_photo"
private val context: Context = context.applicationContext
private var codeCache: String? = null
private var userCache: UserCache? = null
private val _isAuthorized = MutableStateFlow(false)
val isAuthorized: StateFlow<Boolean> = _isAuthorized.asStateFlow()
init {
val prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
codeCache = prefs.getString(KEY_CODE, null)
val name = prefs.getString(KEY_NAME, null)
val photo = prefs.getString(KEY_PHOTO, null)
if (codeCache != null && name != null) {
userCache = UserCache(name, photo)
_isAuthorized.value = true
} else {
clear()
}
}
private fun getPrefs() = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
suspend fun checkAndSave(text: String): Result<Boolean> {
return NetworkDataSource.checkAuth(text).onSuccess { success ->
if (success) {
codeCache = text
_isAuthorized.value = true
getPrefs().edit()
.putString(KEY_CODE, text)
.apply()
}
}.onFailure { exception ->
println("Auth error: ${exception.message}")
}
}
}
fun getCurrentCode(): String? = codeCache
fun saveUserInfo(name: String, photo: String?) {
userCache = UserCache(name, photo)
getPrefs().edit()
.putString(KEY_NAME, name)
.putString(KEY_PHOTO, photo)
.apply()
}
fun getUserInfo(): UserCache? = userCache
fun clear() {
codeCache = null
userCache = null
_isAuthorized.value = false
getPrefs().edit()
.clear()
.apply()
}
}
data class UserCache(
val name: String,
val photo: String?
)

View File

@@ -0,0 +1,26 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.model.PlaceInfo
import ru.myitschool.work.data.source.AuthException
import ru.myitschool.work.data.source.NetworkDataSource
class BookRepository(private val authRepository: AuthRepository) {
suspend fun getAvailableBookings(): Result<Map<String, List<PlaceInfo>>> {
val code = authRepository.getCurrentCode()
return if (code != null) {
NetworkDataSource.getBookingInfo(code)
} else {
Result.failure(AuthException("user not authorized"))
}
}
suspend fun book(date: String, placeId: Int): Result<Unit> {
val code = authRepository.getCurrentCode()
return if (code != null) {
NetworkDataSource.book(code, date, placeId)
} else {
Result.failure(AuthException("user not authorized"))
}
}
}

View File

@@ -0,0 +1,18 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.model.UserInfoResponse
import ru.myitschool.work.data.source.AuthException
import ru.myitschool.work.data.source.NetworkDataSource
class MainRepository(private val authRepository: AuthRepository) {
suspend fun getUserInfo(): Result<UserInfoResponse> {
val code = authRepository.getCurrentCode()
return if (code != null) {
NetworkDataSource.getInfo(code)
} else {
Result.failure(AuthException("user not pass auth"))
}
}
}

View File

@@ -1,42 +1,192 @@
package ru.myitschool.work.data.source
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import ru.myitschool.work.core.Constants
import ru.myitschool.work.data.model.PlaceInfo
import ru.myitschool.work.data.model.UserInfoResponse
import java.net.ConnectException
import java.net.SocketTimeoutException
@Serializable
data class BookRequest(
val date: String,
val placeId: Int
)
object NetworkDataSource {
private val client by lazy {
HttpClient(CIO) {
engine {
requestTimeout = 10000
}
HttpResponseValidator {
validateResponse { response ->
val statusCode = response.status.value
when (statusCode) {
in 400..499 -> {
when (response.status) {
HttpStatusCode.Unauthorized -> {
throw AuthException("Неверный код авторизации")
}
HttpStatusCode.NotFound -> {
throw NotFoundException("Ресурс не найден")
}
HttpStatusCode.Conflict -> {
throw ConflictException("Место уже забронировано")
}
HttpStatusCode.BadRequest -> {
throw BadRequestException("Некорректный запрос")
}
else -> {
val exceptionMessage = response.body<String>()
throw ClientRequestException(
response,
exceptionMessage.ifBlank {
"Клиентская ошибка: $statusCode"
}
)
}
}
}
in 500..599 -> {
throw ServerException("Ошибка сервера: $statusCode")
}
}
}
handleResponseExceptionWithRequest { exception, _ ->
when (exception) {
is SocketTimeoutException -> {
throw NetworkException("Таймаут соединения")
}
is ConnectException -> {
throw NetworkException("Не удалось подключиться к серверу")
}
is HttpRequestTimeoutException -> {
throw NetworkException("Таймаут запроса")
}
}
}
}
install(ContentNegotiation) {
json(
Json {
isLenient = true
ignoreUnknownKeys = true
explicitNulls = true
explicitNulls = false
encodeDefaults = true
}
)
}
defaultRequest {
contentType(ContentType.Application.Json)
}
}
}
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.AUTH_URL))
when (response.status) {
HttpStatusCode.OK -> true
else -> error(response.bodyAsText())
response.status == HttpStatusCode.OK
}.recoverCatching { throwable ->
when (throwable) {
is AuthException -> false
else -> throw throwable
}
}
}
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
}
suspend fun getInfo(code: String): Result<UserInfoResponse> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.INFO_URL))
when (response.status) {
HttpStatusCode.OK -> response.body<UserInfoResponse>()
else -> {
val errorMessage = response.body<String>().ifBlank {
"Ошибка: ${response.status}"
}
throw RuntimeException(errorMessage)
}
}
}
}
suspend fun getBookingInfo(code: String): Result<Map<String, List<PlaceInfo>>> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) {
HttpStatusCode.OK -> {
try {
val body = response.body<Map<String, List<PlaceInfo>>>()
body
} catch (e: Exception) {
emptyMap()
}
}
HttpStatusCode.NoContent -> {
emptyMap()
}
else -> {
val errorMsg = response.body<String>().ifBlank {
"Ошибка загрузки данных: ${response.status}"
}
throw RuntimeException(errorMsg)
}
}
}
}
suspend fun book(code: String, date: String, placeId: Int): Result<Unit> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.post(getUrl(code, Constants.BOOK_URL)) {
setBody(BookRequest(date, placeId))
}
when (response.status) {
HttpStatusCode.OK -> Unit
HttpStatusCode.Created -> Unit
HttpStatusCode.Conflict -> throw ConflictException("Место уже забронировано")
else -> {
val errorMsg = response.body<String>().ifBlank {
"Ошибка бронирования: ${response.status}"
}
throw RuntimeException(errorMsg)
}
}
}
}
private fun getUrl(code: String, targetUrl: String): String {
return "${Constants.HOST}/api/$code$targetUrl"
}
}
class NetworkException(message: String) : Exception(message)
class AuthException(message: String) : Exception(message)
class NotFoundException(message: String) : Exception(message)
class ConflictException(message: String) : Exception(message)
class ServerException(message: String) : Exception(message)
class BadRequestException(message: String) : Exception(message)

View File

@@ -2,48 +2,62 @@ package ru.myitschool.work.ui.screen
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.BookScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.screen.auth.AuthScreen
import ru.myitschool.work.ui.screen.book.BookScreen
import ru.myitschool.work.ui.screen.main.MainScreen
@Composable
fun AppNavHost(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController()
) {
val context = LocalContext.current
val authRepository = remember { AuthRepository.getInstance(context) }
val isAuthorized by authRepository.isAuthorized.collectAsState()
LaunchedEffect(isAuthorized) {
if (isAuthorized) {
navController.navigate(MainScreenDestination) {
popUpTo(0) { inclusive = false }
}
} else {
navController.navigate(AuthScreenDestination) {
popUpTo(0) { inclusive = false }
}
}
}
NavHost(
modifier = modifier,
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None },
navController = navController,
startDestination = AuthScreenDestination,
startDestination = if (isAuthorized) MainScreenDestination else AuthScreenDestination,
) {
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,10 +1,12 @@
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.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.material3.Button
@@ -16,12 +18,11 @@ 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.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -34,35 +35,58 @@ import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable
fun AuthScreen(
viewModel: AuthViewModel = viewModel(),
viewModel: AuthViewModel = viewModel(factory = AuthViewModelFactory(LocalContext.current)),
navController: NavController
) {
val state by viewModel.uiState.collectAsState()
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination)
viewModel.actionFlow.collect { action ->
when (action) {
is AuthAction.NavigateToMain -> {
navController.navigate(MainScreenDestination) {
popUpTo(0)
}
}
}
}
}
Column(
Box(
modifier = Modifier
.fillMaxSize()
.padding(all = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
when (val currentState = state) {
is AuthState.Data -> Content(viewModel, currentState)
is AuthState.Loading -> {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 32.dp)
)
when (val currentState = state) {
is AuthState.Loading -> {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
}
is AuthState.Data -> {
Content(
state = currentState,
// ?????? bug fix
onTextChange = { viewModel.onIntent(AuthIntent.TextInput(it)) },
onSendClick = {
keyboardController?.hide()
viewModel.onIntent(AuthIntent.Send(it))
}
)
}
}
}
}
@@ -70,28 +94,48 @@ fun AuthScreen(
@Composable
private fun Content(
viewModel: AuthViewModel,
state: AuthState.Data
state: AuthState.Data,
onTextChange: (String) -> Unit,
onSendClick: (String) -> Unit
) {
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 = state.code.length == 4 &&
state.code.matches(Regex("^[a-zA-Z0-9]{4}$"))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(stringResource(R.string.auth_sign_in))
if (state.error != null) {
Text(
text = state.error,
color = Color.Red,
modifier = Modifier
.testTag(TestIds.Auth.ERROR)
.padding(bottom = 16.dp)
)
}
TextField(
modifier = Modifier
.testTag(TestIds.Auth.CODE_INPUT)
.fillMaxWidth(),
value = state.code,
onValueChange = onTextChange,
label = { Text(stringResource(R.string.auth_label)) },
singleLine = true,
isError = state.error != null
)
Spacer(modifier = Modifier.height(16.dp))
Button(
modifier = Modifier
.testTag(TestIds.Auth.SIGN_BUTTON)
.fillMaxWidth(),
onClick = { onSendClick(state.code) },
enabled = isButtonEnabled
) {
Text(stringResource(R.string.auth_sign_in))
}
}
}

View File

@@ -1,6 +1,9 @@
package ru.myitschool.work.ui.screen.auth
sealed interface AuthState {
object Loading: AuthState
object Data: AuthState
object Loading : AuthState
data class Data(
val code: String = "",
val error: String? = null
) : AuthState
}

View File

@@ -1,8 +1,9 @@
package ru.myitschool.work.ui.screen.auth
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -13,31 +14,75 @@ import kotlinx.coroutines.launch
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)
class AuthViewModel(
private val authRepository: AuthRepository,
private val checkAndSaveAuthCodeUseCase: CheckAndSaveAuthCodeUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data())
val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow()
val actionFlow: SharedFlow<AuthAction> = _actionFlow
fun onIntent(intent: AuthIntent) {
when (intent) {
is AuthIntent.Send -> {
viewModelScope.launch(Dispatchers.Default) {
_uiState.update { AuthState.Loading }
checkAndSaveAuthCodeUseCase.invoke("9999").fold(
onSuccess = {
_actionFlow.emit(Unit)
},
onFailure = { error ->
error.printStackTrace()
_actionFlow.emit(Unit)
}
if (validateCode(intent.text)) {
viewModelScope.launch {
_uiState.update { AuthState.Loading }
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
onSuccess = {
_actionFlow.emit(AuthAction.NavigateToMain)
},
onFailure = { error ->
_uiState.update {
AuthState.Data(
code = intent.text,
error = error.message ?: "error"
)
}
}
)
}
} else {
_uiState.update {
AuthState.Data(
code = intent.text,
error = "wrong"
)
}
}
}
is AuthIntent.TextInput -> {
_uiState.update {
AuthState.Data(
code = intent.text,
error = null
)
}
}
is AuthIntent.TextInput -> Unit
}
}
private fun validateCode(code: String): Boolean {
return code.length == 4 && code.matches(Regex("^[a-zA-Z0-9]{4}$"))
}
}
class AuthViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AuthViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val useCase = CheckAndSaveAuthCodeUseCase(authRepository)
return AuthViewModel(authRepository, useCase) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
sealed interface AuthAction {
object NavigateToMain : AuthAction
}

View File

@@ -0,0 +1,9 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookIntent {
object Refresh : BookIntent
object Back : BookIntent
object Book : BookIntent
data class SelectDate(val index: Int) : BookIntent
data class SelectPlace(val index: Int) : BookIntent
}

View File

@@ -0,0 +1,255 @@
package ru.myitschool.work.ui.screen.book
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
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.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import androidx.compose.material3.Icon
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BookScreen(
viewModel: BookViewModel = viewModel(factory = BookViewModelFactory(LocalContext.current)),
navController: NavController
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when (action) {
BookAction.NavigateBack -> {
navController.popBackStack()
}
BookAction.NavigateBackWithRefresh -> {
navController.previousBackStackEntry?.savedStateHandle?.set(
"shouldRefresh", true)
navController.popBackStack()
}
}
}
}
when (val currentState = state) {
BookState.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
BookState.Empty -> {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.book_title)) },
navigationIcon = {
IconButton(
onClick = { viewModel.onIntent(BookIntent.Back) },
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON)
) {
Icon(
painter = painterResource(id = R.drawable.back),
contentDescription = null
)
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.book_empty),
modifier = Modifier.testTag(TestIds.Book.EMPTY),
textAlign = TextAlign.Center
)
}
}
}
is BookState.Data -> {
if (currentState.error != null) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.book_title)) },
navigationIcon = {
IconButton(
onClick = { viewModel.onIntent(BookIntent.Back) },
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON)
) {
Icon(
painter = painterResource(id = R.drawable.back),
contentDescription = null
)
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = currentState.error,
modifier = Modifier.testTag(TestIds.Book.ERROR),
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { viewModel.onIntent(BookIntent.Refresh) },
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON)
) {
Text(stringResource(R.string.book_refresh))
}
}
}
} else {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.book_title)) },
navigationIcon = {
IconButton(
onClick = { viewModel.onIntent(BookIntent.Back) },
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON)
) {
Icon(
painter = painterResource(id = R.drawable.back),
contentDescription = null
)
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Content(
state = currentState,
onDateSelect = { viewModel.onIntent(BookIntent.SelectDate(it)) },
onPlaceSelect = { viewModel.onIntent(BookIntent.SelectPlace(it)) },
onBookClick = { viewModel.onIntent(BookIntent.Book) }
)
}
}
}
}
}
}
@Composable
private fun Content(
state: BookState.Data,
onDateSelect: (Int) -> Unit,
onPlaceSelect: (Int) -> Unit,
onBookClick: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
ScrollableTabRow(
selectedTabIndex = state.selectedDateIndex,
modifier = Modifier.fillMaxWidth(),
edgePadding = 0.dp
) {
state.dates.forEachIndexed { index, dateItem ->
Tab(
selected = state.selectedDateIndex == index,
onClick = { onDateSelect(index) },
modifier = Modifier.testTag(TestIds.Book.getIdDateItemByPosition(index))
) {
Column(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Text(
text = dateItem.displayDate,
modifier = Modifier.testTag(TestIds.Book.ITEM_DATE)
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
val selectedDate = state.dates.getOrNull(state.selectedDateIndex)
if (selectedDate != null) {
Column {
selectedDate.places.forEachIndexed { index, placeItem ->
Row(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = state.selectedPlaceIndex == index,
onClick = { onPlaceSelect(index) }
)
.padding(vertical = 8.dp, horizontal = 16.dp)
.testTag(TestIds.Book.getIdPlaceItemByPosition(index))
) {
RadioButton(
selected = state.selectedPlaceIndex == index,
onClick = { onPlaceSelect(index) },
modifier = Modifier.testTag(TestIds.Book.ITEM_PLACE_SELECTOR)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = placeItem.name,
modifier = Modifier.testTag(TestIds.Book.ITEM_PLACE_TEXT),
style = MaterialTheme.typography.bodyLarge
)
}
}
}
}
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = onBookClick,
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Book.BOOK_BUTTON),
enabled = state.selectedPlaceIndex != null
) {
Text(stringResource(R.string.book_book))
}
}
}

View File

@@ -0,0 +1,24 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookState {
object Loading : BookState
data class Data(
val dates: List<DateItem> = emptyList(),
val selectedDateIndex: Int = 0,
val selectedPlaceIndex: Int? = null,
val error: String? = null
) : BookState
object Empty : BookState
}
data class DateItem(
val id: String,
val displayDate: String, // dd.MM
val rawDate: String, // for api
val places: List<PlaceItem>
)
data class PlaceItem(
val id: String,
val name: String
)

View File

@@ -0,0 +1,196 @@
package ru.myitschool.work.ui.screen.book
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
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.launch
import ru.myitschool.work.data.model.PlaceInfo
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.BookRepository
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class BookViewModel(private val bookRepo: BookRepository) : ViewModel() {
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 {
loadData()
}
fun onIntent(intent: BookIntent) {
when (intent) {
BookIntent.Refresh -> {
loadData()
}
BookIntent.Back -> {
viewModelScope.launch {
_actionFlow.emit(BookAction.NavigateBack)
}
}
BookIntent.Book -> {
bookSelected()
}
is BookIntent.SelectDate -> {
_uiState.update { state ->
if (state is BookState.Data) {
state.copy(
selectedDateIndex = intent.index,
selectedPlaceIndex = null
)
} else {
state
}
}
}
is BookIntent.SelectPlace -> {
_uiState.update { state ->
if (state is BookState.Data) {
state.copy(selectedPlaceIndex = intent.index)
} else {
state
}
}
}
}
}
private fun loadData() {
viewModelScope.launch {
_uiState.update { BookState.Loading }
bookRepo.getAvailableBookings().fold(
onSuccess = { response ->
val datesMap = response ?: emptyMap()
val dateItems = datesMap.entries
.sortedBy { (date, _) -> parseDate(date) }
.filter { (_, places) -> places.isNotEmpty() }
.map { (dateString, places) ->
DateItem(
id = dateString,
displayDate = formatDateForDisplay(dateString),
rawDate = dateString,
places = places.map { placeInfo ->
PlaceItem(
id = placeInfo.id.toString(),
name = placeInfo.place
)
}
)
}
if (dateItems.isEmpty()) {
_uiState.update { BookState.Empty }
} else {
_uiState.update {
BookState.Data(
dates = dateItems,
selectedDateIndex = 0
)
}
}
},
onFailure = { error ->
_uiState.update {
BookState.Data(
error = error.message ?: "Ошибка загрузки данных"
)
}
}
)
}
}
private fun bookSelected() {
viewModelScope.launch {
val state = _uiState.value
if (state is BookState.Data) {
val selectedDate = state.dates.getOrNull(state.selectedDateIndex)
val selectedPlaceIndex = state.selectedPlaceIndex
if (selectedDate != null && selectedPlaceIndex != null) {
val selectedPlace = selectedDate.places.getOrNull(selectedPlaceIndex)
if (selectedPlace != null) {
_uiState.update { BookState.Loading }
bookRepo.book(
date = selectedDate.rawDate,
placeId = selectedPlace.id.toInt()
).fold(
onSuccess = {
_actionFlow.emit(BookAction.NavigateBackWithRefresh)
},
onFailure = { error ->
_uiState.update {
state.copy(
error = error.message ?: "Ошибка бронирования"
)
}
}
)
} else {
_uiState.update {
state.copy(
error = "Место не выбрано"
)
}
}
} else {
_uiState.update {
state.copy(
error = "Выберите место для бронирования"
)
}
}
}
}
}
private fun parseDate(dateString: String): Long {
return try {
val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
format.parse(dateString)?.time ?: 0L
} catch (e: Exception) {
0L
}
}
private fun formatDateForDisplay(dateString: String): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd.MM", Locale.getDefault())
val date = inputFormat.parse(dateString)
date?.let { outputFormat.format(it) } ?: dateString
} catch (e: Exception) {
dateString
}
}
}
sealed interface BookAction {
object NavigateBack : BookAction
object NavigateBackWithRefresh : BookAction
}
class BookViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(BookViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val bookRepository = BookRepository(authRepository)
return BookViewModel(bookRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@@ -0,0 +1,8 @@
package ru.myitschool.work.ui.screen.main
sealed interface MainIntent {
object Logout : MainIntent
object Refresh : MainIntent
object AddBooking : MainIntent
data class ItemClick(val position: Int) : MainIntent
}

View File

@@ -0,0 +1,280 @@
package ru.myitschool.work.ui.screen.main
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.BookScreenDestination
@Composable
fun MainScreen(
viewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current)),
navController: NavController
) {
val state by viewModel.uiState.collectAsState()
val shouldRefresh by navController.currentBackStackEntry
?.savedStateHandle
?.getStateFlow<Boolean>("shouldRefresh", false)
?.collectAsState() ?: remember { mutableStateOf(false) }
LaunchedEffect(shouldRefresh) {
if (shouldRefresh) {
viewModel.onIntent(MainIntent.Refresh)
navController.currentBackStackEntry?.savedStateHandle?.remove<Boolean>("shouldRefresh")
}
}
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when (action) {
is MainAction.NavigateToAuth -> {
navController.navigate(AuthScreenDestination) {
popUpTo(0)
}
}
is MainAction.NavigateToBooking -> {
navController.navigate(BookScreenDestination)
}
}
}
}
Box(
modifier = Modifier.fillMaxSize()
) {
when (val currentState = state) {
is MainState.Loading -> {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
is MainState.Data -> {
if (currentState.error != null) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = currentState.error,
color = Color.Red,
modifier = Modifier
.testTag(TestIds.Main.ERROR)
.padding(bottom = 16.dp)
)
Button(
onClick = { viewModel.onIntent(MainIntent.Refresh) },
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON)
) {
Text(stringResource(R.string.main_refresh))
}
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (!currentState.userPhotoUrl.isNullOrEmpty()) {
Image(
painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current)
.data(currentState.userPhotoUrl)
.build()
),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(64.dp)
.testTag(TestIds.Main.PROFILE_IMAGE)
)
} else {
Icon(
painter = painterResource(id = R.drawable.github),
contentDescription = null,
modifier = Modifier
.size(64.dp)
.testTag(TestIds.Main.PROFILE_IMAGE)
)
}
Spacer(modifier = Modifier.size(16.dp))
Column {
Text(
text = currentState.userName,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.testTag(TestIds.Main.PROFILE_NAME)
)
if (currentState.bookings.isNotEmpty()) {
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Забронировано мест: ${currentState.bookings.size}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.onIntent(MainIntent.Logout) },
modifier = Modifier
.weight(1f)
.testTag(TestIds.Main.LOGOUT_BUTTON)
) {
Text(stringResource(R.string.main_logout))
}
Button(
onClick = { viewModel.onIntent(MainIntent.Refresh) },
modifier = Modifier
.weight(1f)
.testTag(TestIds.Main.REFRESH_BUTTON)
) {
Text(stringResource(R.string.main_refresh))
}
Button(
onClick = { viewModel.onIntent(MainIntent.AddBooking) },
modifier = Modifier
.weight(1f)
.testTag(TestIds.Main.ADD_BUTTON)
) {
Text(stringResource(R.string.main_add_booking))
}
}
if (currentState.bookings.isNotEmpty()) {
Text(
text = "Мои бронирования:",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
itemsIndexed(currentState.bookings) { index, booking ->
BookingItem(
booking = booking,
position = index,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.testTag(TestIds.Main.getIdItemByPosition(index))
)
}
}
} else {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.Center
) {
Text(
text = "У вас нет активных бронирований",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}
}
}
@Composable
private fun BookingItem(
booking: BookingItem,
position: Int,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = booking.place,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE)
)
Text(
text = booking.getFormattedDate(),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.testTag(TestIds.Main.ITEM_DATE)
)
}
}
}
}

View File

@@ -0,0 +1,24 @@
package ru.myitschool.work.ui.screen.main
import java.time.LocalDate
import java.time.format.DateTimeFormatter
sealed interface MainState {
object Loading : MainState
data class Data(
val userName: String = "",
val userPhotoUrl: String? = null,
val bookings: List<BookingItem> = emptyList(),
val error: String? = null
) : MainState
}
data class BookingItem(
val id: String,
val date: LocalDate,
val place: String
) {
fun getFormattedDate(): String {
return date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
}
}

View File

@@ -0,0 +1,112 @@
package ru.myitschool.work.ui.screen.main
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
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.launch
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.MainRepository
class MainViewModel(
private val authRepo: AuthRepository,
private val mainRepo: MainRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<MainState>(MainState.Loading)
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<MainAction> = MutableSharedFlow()
val actionFlow: SharedFlow<MainAction> = _actionFlow
private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
init {
loadData()
}
fun onIntent(intent: MainIntent) {
when (intent) {
MainIntent.Logout -> {
authRepo.clear()
viewModelScope.launch {
_actionFlow.emit(MainAction.NavigateToAuth)
}
}
MainIntent.Refresh -> {
loadData()
}
MainIntent.AddBooking -> {
viewModelScope.launch {
_actionFlow.emit(MainAction.NavigateToBooking)
}
}
is MainIntent.ItemClick -> {
}
}
}
private fun loadData() {
viewModelScope.launch {
_uiState.update { MainState.Loading }
mainRepo.getUserInfo().fold(
onSuccess = { userInfo ->
val bookings = userInfo.bookings.mapNotNull { bookingResponse ->
try {
BookingItem(
id = bookingResponse.bookingId.toString(),
date = LocalDate.parse(bookingResponse.date, dateFormatter),
place = bookingResponse.place
)
} catch (e: Exception) {
null
}
}.sortedBy { it.date }
authRepo.saveUserInfo(userInfo.name, userInfo.photoUrl)
_uiState.update {
MainState.Data(
userName = userInfo.name,
userPhotoUrl = userInfo.photoUrl,
bookings = bookings
)
}
},
onFailure = { error ->
_uiState.update {
MainState.Data(
userName = "",
userPhotoUrl = null,
bookings = emptyList(),
error = error.message ?: "Ошибка загрузки данных"
)
}
}
)
}
}
}
class MainViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val mainRepository = MainRepository(authRepository)
return MainViewModel(authRepository, mainRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
sealed interface MainAction {
object NavigateToAuth : MainAction
object NavigateToBooking : MainAction
}

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

@@ -4,4 +4,13 @@
<string name="auth_title">Привет! Введи код для авторизации</string>
<string name="auth_label">Код</string>
<string name="auth_sign_in">Войти</string>
<string name="main_title">Мои бронирования</string>
<string name="main_logout">Выйти</string>
<string name="main_refresh">Обновить</string>
<string name="main_add_booking">Забронировать</string>
<string name="book_title">Бронирование</string>
<string name="book_back">Назад</string>
<string name="book_book">Забронировать</string>
<string name="book_refresh">Повторить</string>
<string name="book_empty">Всё забронировано</string>
</resources>