Compare commits

...

5 Commits

Author SHA1 Message Date
CryptoDruid802
caff70568d new fun: exit 2025-12-11 22:03:28 +03:00
CryptoDruid802
56c14cbac5 new fun: exit 2025-12-11 21:53:38 +03:00
72e8b946c0 feat: edit main screen - add template texts 2025-12-01 22:01:00 +03:00
dbc4830418 feat: edit auth screen 2025-11-30 22:22:20 +03:00
783f25ced6 feat: add templates of screens 2025-11-29 22:36:51 +03:00
18 changed files with 540 additions and 46 deletions

View File

@@ -35,6 +35,7 @@ android {
}
dependencies {
implementation("androidx.compose.material3:material3:1.4.0")
defaultComposeLibrary()
implementation("androidx.datastore:datastore-preferences:1.1.7")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
@@ -48,4 +49,5 @@ dependencies {
implementation("io.ktor:ktor-client-content-negotiation:$ktor")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("io.coil-kt:coil-compose:2.6.0")
}

View File

@@ -19,10 +19,9 @@
android:name=".ui.root.RootActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:label="@string/title_activity_root">
android:theme="@style/Theme.Work">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View File

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

View File

@@ -4,7 +4,6 @@ import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers
@@ -29,14 +28,21 @@ object NetworkDataSource {
}
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
runCatching {
val response = client.get(getUrl(code, Constants.AUTH_URL))
when (response.status) {
HttpStatusCode.OK -> true
else -> error(response.bodyAsText())
HttpStatusCode.Unauthorized -> error("Код не существует")
HttpStatusCode.BadRequest -> error("Что-то пошло не так")
else -> error("Неизвестная ошибка: ${response.status}")
}
}.mapCatching { success ->
success
}.recoverCatching { _ ->
throw Exception("Не удалось соединиться с сервером")
}
}
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
}

View File

@@ -15,6 +15,8 @@ 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(
@@ -26,24 +28,17 @@ fun AppNavHost(
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None },
navController = navController,
startDestination = AuthScreenDestination,
// startDestination = AuthScreenDestination,
startDestination = MainScreenDestination,
) {
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

@@ -31,6 +31,13 @@ import androidx.navigation.NavController
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.ui.nav.MainScreenDestination
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.imePadding
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@Composable
fun AuthScreen(
@@ -48,49 +55,90 @@ fun AuthScreen(
Column(
modifier = Modifier
.fillMaxSize()
.padding(all = 24.dp),
.imePadding()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.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)
when (state) {
is AuthState.Loading -> {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
CircularProgressIndicator(modifier = Modifier.size(64.dp))
}
else -> {
Content(viewModel, state)
}
}
}
}
@Composable
private fun Content(
viewModel: AuthViewModel,
state: AuthState.Data
state: AuthState
) {
var inputText by remember { mutableStateOf("") }
val isButtonEnabled =
inputText.length == 4 && inputText.all { it.isLetterOrDigit() }
Text(
text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.size(16.dp))
TextField(
modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Auth.CODE_INPUT),
value = inputText,
onValueChange = {
inputText = it
if (it.length <= 4 && it.all { ch -> ch.isLetterOrDigit() }) {
inputText = it
}
viewModel.onIntent(AuthIntent.TextInput(it))
},
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
viewModel.onIntent(AuthIntent.Send(inputText))
}
),
singleLine = true,
label = { Text(stringResource(R.string.auth_label)) }
)
Spacer(modifier = Modifier.size(12.dp))
AnimatedVisibility(
visible = state is AuthState.Error,
enter = fadeIn(),
exit = fadeOut()
) {
Text(
text = (state as AuthState.Error).message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.size(16.dp))
Button(
modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Auth.SIGN_BUTTON),
onClick = {
viewModel.onIntent(AuthIntent.Send(inputText))
},
enabled = true
enabled = isButtonEnabled
) {
Text(stringResource(R.string.auth_sign_in))
}

View File

@@ -1,6 +1,7 @@
package ru.myitschool.work.ui.screen.auth
sealed interface AuthState {
object Loading: AuthState
object Data: AuthState
sealed class AuthState {
data object Data : AuthState()
data object Loading : AuthState()
data class Error(val message: String) : AuthState()
}

View File

@@ -13,31 +13,39 @@ 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) }
class AuthViewModel() : ViewModel() {
private val checkAndSaveAuthCodeUseCase by lazy {
CheckAndSaveAuthCodeUseCase(AuthRepository)
}
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data)
val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
private val _actionFlow = MutableSharedFlow<Unit>()
val actionFlow: SharedFlow<Unit> = _actionFlow
fun onIntent(intent: AuthIntent) {
when (intent) {
is AuthIntent.Send -> {
viewModelScope.launch(Dispatchers.Default) {
_uiState.update { AuthState.Loading }
checkAndSaveAuthCodeUseCase.invoke("9999").fold(
viewModelScope.launch(Dispatchers.IO) {
_uiState.value = AuthState.Loading
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
onSuccess = {
_actionFlow.emit(Unit)
},
onFailure = { error ->
error.printStackTrace()
_actionFlow.emit(Unit)
onFailure = { throwable ->
val errorMessage = throwable.message ?: "Неизвестная ошибка"
_uiState.value = AuthState.Error(errorMessage)
}
)
}
}
is AuthIntent.TextInput -> Unit
is AuthIntent.TextInput -> {
_uiState.value = AuthState.Data
}
}
}
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookIntent {
data class Send(val text: String): BookIntent
data class TextInput(val text: String): BookIntent
}

View File

@@ -0,0 +1,71 @@
package ru.myitschool.work.ui.screen.book
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable
fun BookScreen(
viewModel: BookViewModel = viewModel(),
navController: NavController
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination)
}
}
/*
Экран бронирования
На данном экране необходимо вывести возможные даты и места для бронирования.
Элементы, которые должны присутствовать на экране:
Группа вкладок. Каждая вкладка (book_date_pos_{индекс}) содержит текстовое поле (book_date) с датой бронирования в формате dd.MM.
В зависимости от выбранной даты необходимо отобразить группу с единственным выбором (пояснения на изображении ниже). Каждый элемент группы (book_place_pos_{индекс}) кликабелен и содержит:
Текстовое поле (book_place_text), в котором содержится место доступное для брони.
Селектор (book_place_selector), который отображает, выбран элемент или нет. У данного элемента обязательно наличие: (Modifier.selectable)
Кнопка (book_book_button) для бронирования.
Кнопка (book_back_button) для возвращения на предыдущий экран.
По умолчанию неотображаемое текстовое поле с ошибкой (book_error). Отметим, что это поле не должно рендериться.
По умолчанию неотображаемая кнопка обновить (book_refresh_button).
По умолчанию неотображаемый текст “Всё забронировано” (book_empty).
Требования к компонентам:
По умолчанию выбирается самая ранняя доступная дата (например, из набора "5 января", "6 января", "9 января" будет показана дата "5 января").
Список дат отсортирован по возрастанию. Даты без доступных мест для бронирования необходимо не отображать.
Если нет доступных для бронирования дат, необходимо скрыть все элементы, кроме элементов из п. 4 и 7.
В случае ошибки при получении данных о доступном бронировании в запросе api/<CODE>/booking, необходимо отобразить элемент из п. 4, 5 и 6 с возможностью обновить данные.
При успешном бронировании нужно закрыть текущий экран и вернуться на главный, обновив его.
*/
Column(
modifier = Modifier
.fillMaxSize()
.padding(all = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "страница бронирования...",
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center
)
}
}

View File

@@ -0,0 +1,5 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookState {
object Data: BookState
}

View File

@@ -0,0 +1,39 @@
package ru.myitschool.work.ui.screen.book
import androidx.lifecycle.ViewModel
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 ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
class BookViewModel : ViewModel() {
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val _uiState = MutableStateFlow<BookState>(BookState.Data)
val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
fun onIntent(intent: BookIntent) {
// when (intent) {
// is MainIntent.Send -> {
// viewModelScope.launch(Dispatchers.Default) {
// _uiState.update { MainState.Loading }
// checkAndSaveAuthCodeUseCase.invoke("9999").fold(
// onSuccess = {
// _actionFlow.emit(Unit)
// },
// onFailure = { error ->
// error.printStackTrace()
// _actionFlow.emit(Unit)
// }
// )
// }
// }
// is MainIntent.TextInput -> Unit
// }
}
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.main
sealed interface MainIntent {
data class Send(val text: String): MainIntent
data class TextInput(val text: String): MainIntent
}

View File

@@ -0,0 +1,246 @@
package ru.myitschool.work.ui.screen.main
import androidx.compose.foundation.layout.Arrangement
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.imePadding
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.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
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.graphics.Color
import androidx.compose.ui.layout.ContentScale
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 coil3.compose.AsyncImage
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable
fun MainScreen(
viewModel: MainViewModel = viewModel(),
navController: NavController
) {
val state by viewModel.uiState.collectAsState()
/*
По умолчанию скрытое текстовое поле с ошибкой (main_error).
Требования к компонентам:
В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных.
Для получения данных необходимо использовать сетевой запрос /api/<CODE>/info.
При нажатии на кнопку для выхода, все сохранённые данные пользователя должны быть очищены, а приложение должно открыть экран авторизации.ГОТОВО
При нажатии кнопки бронирования необходимо открыть экран бронирования.
При нажатии на кнопку обновления данных — необходимо повторно вызывать сетевой запрос для получения актуальных данных.
Список бронирований должен быть отсортирован в порядке увеличения даты (например, 5 января -> 6 января -> 9 января).
*/
val bookings = listOf(
Booking(date = "2025-12-01", place = "Аудитория 1"),
Booking(date = "2025-12-01", place = "Аудитория 2"),
Booking(date = "2025-12-02", place = "Аудитория 3"),
Booking(date = "2025-12-02", place = "Конференц-зал"),
Booking(date = "2025-12-03", place = "Аудитория с очень длинным названием. Lorem ipsum"),
Booking(date = "2025-12-03", place = "Лаборатория №101"),
Booking(date = "2025-12-04", place = "Переговорная комната"),
Booking(date = "2025-12-04", place = "Спортивный зал"),
)
LaunchedEffect(Unit) {
viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination)
}
}
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(
start = 20.dp,
top = 20.dp,
end = 20.dp,
bottom = 0.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
model = "https://palyulin.ru/netcat_files/23/21/rabotnik.jpg",
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
.testTag(TestIds.Main.PROFILE_IMAGE)
)
Spacer(modifier = Modifier.size(16.dp))
Text(
text = "Иванов Иван Иванович",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier
.testTag(TestIds.Main.PROFILE_NAME)
)
}
Spacer(modifier = Modifier.size(8.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Main.LOGOUT_BUTTON),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError
),
onClick = {
AuthRepository.clearCode()
navController.navigate(AuthScreenDestination)
},
) {
Text(stringResource(R.string.logout))
}
Spacer(modifier = Modifier.size(8.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Main.REFRESH_BUTTON),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
onClick = {
},
) {
Text(stringResource(R.string.refresh))
}
Spacer(modifier = Modifier.size(8.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.testTag(TestIds.Main.ADD_BUTTON),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF2E7D32),
contentColor = Color.White
),
onClick = {
},
) {
Text(stringResource(R.string.book_new))
}
Scaffold(
modifier = Modifier.fillMaxSize()
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
itemsIndexed(
items = bookings,
key = { index, item -> "main_book_pos_$index" }
) { index, booking ->
BookCard(
date = booking.date,
place = booking.place,
modifier = Modifier.padding(
top = if (index == 0) 0.dp else 8.dp,
bottom = if (index == bookings.lastIndex) 0.dp else 8.dp
)
)
}
}
}
}
}
data class Booking(
val date: String,
val place: String
)
@Composable
fun BookCard(
date: String,
place: String,
modifier: Modifier = Modifier
) {
val formattedDate = remember(date) {
try {
val parts = date.split("-")
if (parts.size == 3) {
"${parts[2]}.${parts[1]}.${parts[0]}"
} else {
date
}
} catch (_: Exception) {
date
}
}
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
),
modifier = modifier.fillMaxWidth()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "Бронь на $formattedDate",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.testTag(TestIds.Main.ITEM_DATE)
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = place,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.testTag(TestIds.Main.ITEM_PLACE)
)
}
}
}

View File

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

View File

@@ -0,0 +1,43 @@
package ru.myitschool.work.ui.screen.main
import androidx.lifecycle.ViewModel
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.launch
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
class MainViewModel : ViewModel() {
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
private val _uiState = MutableStateFlow<MainState>(MainState.Data)
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
fun onIntent(intent: MainIntent) {
// when (intent) {
// is MainIntent.Send -> {
// viewModelScope.launch(Dispatchers.Default) {
// _uiState.update { MainState.Loading }
// checkAndSaveAuthCodeUseCase.invoke("9999").fold(
// onSuccess = {
// _actionFlow.emit(Unit)
// },
// onFailure = { error ->
// error.printStackTrace()
// _actionFlow.emit(Unit)
// }
// )
// }
// }
// is MainIntent.TextInput -> Unit
// }
}
}

View File

@@ -1,7 +1,9 @@
<resources>
<string name="app_name">Work</string>
<string name="title_activity_root">RootActivity</string>
<string name="auth_title">Привет! Введи код для авторизации</string>
<string name="auth_label">Код</string>
<string name="auth_sign_in">Войти</string>
<string name="logout">Выйти</string>
<string name="refresh">Обновить данные</string>
<string name="book_new">Новая бронь</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Work" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>