Коммит 80853b0a создал по автору Radch-enko's avatar Radch-enko
Просмотр файлов

Feature: implement MainScreenView and enhance core UI functionality

- Added `MainScreenView` with support for room selection, fast booking, and event cancellation.
- Introduced `Intent`, `State`, and `Label` sealed interfaces for structured state management.
- Implemented `MainComponent` to handle intents, manage state, and fetch room data.
- Integrated `epic-calendar-compose` library for date and time selection.
- Added vector drawable resources for UI elements (e.g., arrow, calendar, loader).
- Updated Gradle build script to include compose resource generation and necessary dependencies.
- Enhanced multilanguage support with Russian string resources for booking and room UI elements.
владелец 49be75a4
plugins {
id("band.effective.office.client.kmp.ui")
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.epicarchitect:epic-calendar-compose:1.0.8")
implementation(libs.kotlinx.datetime)
}
}
}
compose.resources {
publicResClass = true
packageOfResClass = "band.effective.office.tablet.core.ui"
generateResClass = auto
}
\ Нет новой строки в конце файла
<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="M13.535,15.001L18.839,20.304C19.327,20.792 19.327,21.584 18.839,22.072C18.351,22.56 17.559,22.56 17.071,22.072L10.707,15.708C10.317,15.318 10.317,14.684 10.707,14.294L17.071,7.93C17.559,7.442 18.351,7.442 18.839,7.93C19.327,8.418 19.327,9.209 18.839,9.698L13.535,15.001Z"
android:fillColor="#6E6E6E"/>
</vector>
<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="M16.464,15.001L11.161,9.698C10.673,9.209 10.673,8.418 11.161,7.93C11.649,7.442 12.441,7.442 12.929,7.93L19.293,14.294C19.683,14.684 19.683,15.318 19.293,15.708L12.929,22.072C12.441,22.56 11.649,22.56 11.161,22.072C10.673,21.584 10.673,20.792 11.161,20.304L16.464,15.001Z"
android:fillColor="#6E6E6E"/>
</vector>
<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="M15,16.464L20.303,11.161C20.791,10.673 21.583,10.673 22.071,11.161C22.559,11.649 22.559,12.441 22.071,12.929L15.707,19.293C15.316,19.683 14.683,19.683 14.292,19.293L7.929,12.929C7.44,12.441 7.44,11.649 7.929,11.161C8.417,10.673 9.208,10.673 9.696,11.161L15,16.464Z"
android:fillColor="#777777"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="31dp"
android:height="30dp"
android:viewportWidth="31"
android:viewportHeight="30">
<path
android:pathData="M21.438,4.45V2.5C21.438,1.987 21.013,1.563 20.5,1.563C19.987,1.563 19.563,1.987 19.563,2.5V4.375H11.438V2.5C11.438,1.987 11.012,1.563 10.5,1.563C9.988,1.563 9.563,1.987 9.563,2.5V4.45C6.188,4.762 4.55,6.775 4.3,9.762C4.275,10.125 4.575,10.425 4.925,10.425H26.075C26.438,10.425 26.737,10.113 26.7,9.762C26.45,6.775 24.813,4.762 21.438,4.45ZM25.5,12.3H5.5C4.813,12.3 4.25,12.863 4.25,13.55V21.25C4.25,25 6.125,27.5 10.5,27.5H20.5C24.875,27.5 26.75,25 26.75,21.25V13.55C26.75,12.863 26.188,12.3 25.5,12.3ZM12.012,22.763C11.95,22.813 11.887,22.875 11.825,22.913C11.75,22.962 11.675,23 11.6,23.025C11.525,23.063 11.45,23.087 11.375,23.1C11.288,23.112 11.212,23.125 11.125,23.125C10.962,23.125 10.8,23.087 10.65,23.025C10.488,22.962 10.363,22.875 10.238,22.763C10.012,22.525 9.875,22.2 9.875,21.875C9.875,21.55 10.012,21.225 10.238,20.987C10.363,20.875 10.488,20.788 10.65,20.725C10.875,20.625 11.125,20.6 11.375,20.65C11.45,20.663 11.525,20.688 11.6,20.725C11.675,20.75 11.75,20.788 11.825,20.837L12.012,20.987C12.238,21.225 12.375,21.55 12.375,21.875C12.375,22.2 12.238,22.525 12.012,22.763ZM12.012,18.388C11.775,18.612 11.45,18.75 11.125,18.75C10.8,18.75 10.475,18.612 10.238,18.388C10.012,18.15 9.875,17.825 9.875,17.5C9.875,17.175 10.012,16.85 10.238,16.612C10.587,16.263 11.137,16.15 11.6,16.35C11.762,16.413 11.9,16.5 12.012,16.612C12.238,16.85 12.375,17.175 12.375,17.5C12.375,17.825 12.238,18.15 12.012,18.388ZM16.388,22.763C16.15,22.987 15.825,23.125 15.5,23.125C15.175,23.125 14.85,22.987 14.613,22.763C14.387,22.525 14.25,22.2 14.25,21.875C14.25,21.55 14.387,21.225 14.613,20.987C15.075,20.525 15.925,20.525 16.388,20.987C16.612,21.225 16.75,21.55 16.75,21.875C16.75,22.2 16.612,22.525 16.388,22.763ZM16.388,18.388L16.2,18.538C16.125,18.587 16.05,18.625 15.975,18.65C15.9,18.688 15.825,18.712 15.75,18.725C15.663,18.737 15.587,18.75 15.5,18.75C15.175,18.75 14.85,18.612 14.613,18.388C14.387,18.15 14.25,17.825 14.25,17.5C14.25,17.175 14.387,16.85 14.613,16.612C14.725,16.5 14.863,16.413 15.025,16.35C15.488,16.15 16.038,16.263 16.388,16.612C16.612,16.85 16.75,17.175 16.75,17.5C16.75,17.825 16.612,18.15 16.388,18.388ZM20.763,22.763C20.525,22.987 20.2,23.125 19.875,23.125C19.55,23.125 19.225,22.987 18.987,22.763C18.763,22.525 18.625,22.2 18.625,21.875C18.625,21.55 18.763,21.225 18.987,20.987C19.45,20.525 20.3,20.525 20.763,20.987C20.987,21.225 21.125,21.55 21.125,21.875C21.125,22.2 20.987,22.525 20.763,22.763ZM20.763,18.388L20.575,18.538C20.5,18.587 20.425,18.625 20.35,18.65C20.275,18.688 20.2,18.712 20.125,18.725C20.038,18.737 19.95,18.75 19.875,18.75C19.55,18.75 19.225,18.612 18.987,18.388C18.763,18.15 18.625,17.825 18.625,17.5C18.625,17.175 18.763,16.85 18.987,16.612C19.112,16.5 19.237,16.413 19.4,16.35C19.625,16.25 19.875,16.225 20.125,16.275C20.2,16.288 20.275,16.313 20.35,16.35C20.425,16.375 20.5,16.413 20.575,16.462L20.763,16.612C20.987,16.85 21.125,17.175 21.125,17.5C21.125,17.825 20.987,18.15 20.763,18.388Z"
android:fillColor="#FAFAFA"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8.001,6.233L13.304,0.93C13.792,0.442 14.584,0.442 15.072,0.93C15.56,1.418 15.56,2.209 15.072,2.698L9.769,8.001L15.072,13.304C15.56,13.792 15.56,14.584 15.072,15.072C14.584,15.56 13.792,15.56 13.304,15.072L8.001,9.769L2.698,15.072C2.209,15.56 1.418,15.56 0.93,15.072C0.442,14.584 0.442,13.792 0.93,13.304L6.233,8.001L0.93,2.698C0.442,2.209 0.442,1.418 0.93,0.93C1.418,0.442 2.209,0.442 2.698,0.93L8.001,6.233Z"
android:fillColor="#FAFAFA"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="65dp"
android:height="64dp"
android:viewportWidth="65"
android:viewportHeight="64">
<group>
<clip-path
android:pathData="M0.5,0h64v64h-64z"/>
<path
android:pathData="M15.167,5.333C13.822,5.333 12.687,6.334 12.521,7.669L8.521,39.669C8.507,39.779 8.5,39.889 8.5,40V56C8.5,57.472 9.694,58.666 11.167,58.666H53.833C55.306,58.666 56.5,57.472 56.5,56V40C56.5,39.889 56.493,39.779 56.479,39.669L52.479,7.669C52.313,6.334 51.178,5.333 49.833,5.333H15.167ZM13.833,42.666H51.167V53.333H13.833V42.666ZM40.5,45.333H45.833V50.666H40.5V45.333ZM35.167,45.333H29.833V50.666H35.167V45.333Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M1.027,-9.061L65.027,54.939"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#1E1C1A"/>
<path
android:pathData="M5.827,3.206L61.294,58.673"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#ffffff"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="36dp"
android:viewportWidth="20"
android:viewportHeight="36">
<path
android:pathData="M0.748,35.844C0.748,25.985 0.748,10.015 0.748,0.156C0.748,10.015 9.319,18 19.901,18C9.319,18 0.748,25.985 0.748,35.844Z"
android:fillColor="#ffffff"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="disconnect">Нет соединения с сервером</string>
<string name="reset">Перезагрузите приложение</string>
<string name="reset_button">Перезагрузить приложение</string>
<string name="select_date_tine_title">Когда</string>
<string name="select_length_title">На сколько</string>
<string name="select_organizer_title">Организатор</string>
<string name="start_date_time">${date} с ${time}</string>
<string name="minus_date_button_string">- 15 мин</string>
<string name="plus_date_button_string">+ 30 мин</string>
<string name="short_minuets">мин</string>
<string name="short_hours">ч</string>
<string name="selectbox_organizer_title">Выберите организатора</string>
<string name="selectbox_organizer_error">Неверный организатор, попробуйте еще раз</string>
<string name="timepicker_view_title"> Занять с </string>
<string name="apply_date_time_for_booking"> Подтвердить </string>
</resources>
\ Нет новой строки в конце файла
package band.effective.office.tablet.core.ui.date
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import band.effective.office.tablet.core.ui.Res
import band.effective.office.tablet.core.ui.arrow_left
import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
import band.effective.office.tablet.core.ui.theme.header6
import epicarchitect.calendar.compose.basis.BasisDayOfMonthContent
import epicarchitect.calendar.compose.basis.BasisDayOfWeekContent
import epicarchitect.calendar.compose.basis.config.LocalBasisEpicCalendarConfig
import epicarchitect.calendar.compose.basis.contains
import epicarchitect.calendar.compose.basis.localizedBySystem
import epicarchitect.calendar.compose.basis.state.LocalBasisEpicCalendarState
import epicarchitect.calendar.compose.datepicker.EpicDatePicker
import epicarchitect.calendar.compose.datepicker.state.EpicDatePickerState
import epicarchitect.calendar.compose.datepicker.state.LocalEpicDatePickerState
import epicarchitect.calendar.compose.pager.state.EpicCalendarPagerState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import org.jetbrains.compose.resources.painterResource
@Composable
fun DatePickerView(
modifier: Modifier = Modifier,
epicDatePickerState: EpicDatePickerState,
) {
val coroutineScope = rememberCoroutineScope()
Column(modifier = modifier) {
DatePickerTitleView(
epicDatePickerState = epicDatePickerState,
onClickNextMonth = {
scrollMonth(
coroutineScope = coroutineScope,
pagerState = epicDatePickerState.pagerState,
amount = 1
)
},
onClickPreviousMonth = {
scrollMonth(
coroutineScope = coroutineScope,
pagerState = epicDatePickerState.pagerState,
amount = -1
)
}
)
EpicDatePicker(
state = epicDatePickerState,
dayOfWeekContent = CustomDayOfWeekContent,
dayOfMonthContent = CustomDayOfMonthContent,
modifier = Modifier.background(Color.Transparent),
)
}
}
@Composable
private fun DatePickerTitleView(
epicDatePickerState: EpicDatePickerState,
onClickNextMonth: () -> Unit,
onClickPreviousMonth: () -> Unit
) {
Row(
modifier = Modifier
.background(
LocalCustomColorsPalette.current.mountainBackground,
RoundedCornerShape(12.dp)
)
.fillMaxWidth().fillMaxHeight(0.15f)
.clip(RoundedCornerShape(12.dp)),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Button(
modifier = Modifier.fillMaxHeight(),
onClick = { onClickPreviousMonth() },
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
)
) {
Image(
modifier = Modifier,
painter = painterResource(Res.drawable.arrow_left),
contentDescription = null
)
}
Text(
text = if (epicDatePickerState.selectedDates.isNotEmpty()) {
stringFormat(epicDatePickerState.selectedDates.firstOrNull())
} else {
epicDatePickerState.pagerState.currentMonth.month.toString() // TODO
},
style = header6,
color = LocalCustomColorsPalette.current.primaryTextAndIcon,
)
Button(
modifier = Modifier.fillMaxHeight(),
onClick = { onClickNextMonth() },
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
)
) {
Image(
modifier = Modifier,
painter = painterResource(Res.drawable.arrow_left),
contentDescription = null
)
}
}
}
private fun scrollMonth(
coroutineScope: CoroutineScope,
pagerState: EpicCalendarPagerState,
amount: Int
) {
coroutineScope.launch {
pagerState.scrollMonths(amount)
}
}
private val CustomDayOfWeekContent: BasisDayOfWeekContent = { dayOfWeek ->
val config = LocalBasisEpicCalendarConfig.current
Text(
text = dayOfWeek.localizedBySystem(),
textAlign = TextAlign.Center,
color = config.contentColor,
style = header6,
maxLines = 1
)
}
private val CustomDayOfMonthContent: BasisDayOfMonthContent = { date ->
val basisState = LocalBasisEpicCalendarState.current!!
val pickerState = LocalEpicDatePickerState.current!!
val selectedDays = pickerState.selectedDates
val selectionMode = pickerState.selectionMode
val isSelected = remember(selectionMode, selectedDays, date) {
when (selectionMode) {
is EpicDatePickerState.SelectionMode.Range -> {
if (selectedDays.isEmpty()) false
else date in selectedDays.min()..selectedDays.max()
}
is EpicDatePickerState.SelectionMode.Single -> {
date in selectedDays
}
}
}
Text(
modifier = Modifier.alpha(
if (date in basisState.currentMonth) 1.0f
else 0.5f
),
text = date.dayOfMonth.toString(),
textAlign = TextAlign.Center,
style = header6,
maxLines = 1,
color = if (isSelected) pickerState.config.selectionContentColor
else pickerState.config.pagerConfig.basisConfig.contentColor
)
}
private fun stringFormat(date: LocalDate?): String {
val monthName = date?.month
return "${date?.dayOfMonth} $monthName".lowercase()
}
\ Нет новой строки в конце файла
package band.effective.office.tablet.core.ui.date
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import band.effective.office.tablet.core.ui.Res
import band.effective.office.tablet.core.ui.arrow_left
import band.effective.office.tablet.core.ui.arrow_right
import band.effective.office.tablet.core.ui.select_date_tine_title
import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
import band.effective.office.tablet.core.ui.theme.h6
import band.effective.office.tablet.core.ui.theme.h8
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@OptIn(ExperimentalTime::class)
@Composable
fun DateTimeView(
modifier: Modifier,
selectDate: Instant,
currentDate: Instant? = null,
back: () -> Unit = {},
increment: () -> Unit,
decrement: () -> Unit,
onOpenDateTimePickerModal: () -> Unit,
showTitle: Boolean = false
) {
val backButtonWeight = when {
currentDate == null -> 0f
currentDate != selectDate -> 1.5f
else -> 0f
}
val timeDayMonthDateFormat = remember { dateTimeFormat }
val dayMonthDateFormat = remember { dayMonthFormat }
Column(modifier = modifier) {
if (showTitle) {
Text(
text = stringResource(Res.string.select_date_tine_title),
color = LocalCustomColorsPalette.current.secondaryTextAndIcon,
style = MaterialTheme.typography.h8
)
Spacer(modifier = Modifier.height(10.dp))
}
Row {
Button(
modifier = Modifier.fillMaxHeight().weight(1f).clip(RoundedCornerShape(15.dp)),
onClick = { decrement() },
colors = ButtonDefaults.buttonColors(
containerColor = LocalCustomColorsPalette.current.elevationBackground
)
) {
Image(
modifier = Modifier,
painter = painterResource(Res.drawable.arrow_left),
contentDescription = null
)
}
Spacer(modifier = Modifier.width(10.dp))
Button(
modifier = Modifier.fillMaxHeight().weight(4f - backButtonWeight)
.clip(RoundedCornerShape(15.dp)),
onClick = { onOpenDateTimePickerModal() },
colors = ButtonDefaults.buttonColors(
containerColor = LocalCustomColorsPalette.current.elevationBackground
)
) {
Text(
text = timeDayMonthDateFormat.format(selectDate.toLocalDateTime(TimeZone.currentSystemDefault())),
style = MaterialTheme.typography.h6
)
}
Spacer(modifier = Modifier.width(10.dp))
if (backButtonWeight > 0) {
Button(
modifier = Modifier.fillMaxHeight().weight(backButtonWeight)
.clip(RoundedCornerShape(15.dp))
.border(3.dp, Color.White, RoundedCornerShape(15.dp)),
onClick = back,
colors = ButtonDefaults.buttonColors(
containerColor = LocalCustomColorsPalette.current.elevationBackground
)
) {
Text(
text = dayMonthDateFormat.format(currentDate?.toLocalDateTime(TimeZone.UTC)!!),
style = MaterialTheme.typography.h6,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.width(10.dp))
}
Button(
modifier = Modifier.fillMaxHeight().weight(1f).clip(RoundedCornerShape(15.dp)),
onClick = { increment() },
colors = ButtonDefaults.buttonColors(
containerColor = LocalCustomColorsPalette.current.elevationBackground
)
) {
Image(
modifier = Modifier,
painter = painterResource(Res.drawable.arrow_right),
contentDescription = null
)
}
}
}
}
package band.effective.office.tablet.core.ui.date
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.format
import kotlinx.datetime.format.MonthNames
import kotlinx.datetime.format.Padding
import kotlinx.datetime.format.char
val dateTimeFormat = LocalDateTime.Format {
monthNumber(padding = Padding.SPACE)
char('/')
dayOfMonth(padding = Padding.ZERO)
char(' ')
year()
char(' ')
hour(padding = Padding.ZERO)
char(':')
minute(padding = Padding.ZERO)
}
val dayMonthFormat = LocalDateTime.Format {
dayOfMonth(padding = Padding.ZERO)
char(' ')
monthName(MonthNames.ENGLISH_FULL)
}
val timeFormatter = LocalDateTime.Format {
hour(padding = Padding.ZERO)
char(':')
minute(padding = Padding.ZERO)
}
fun LocalDateTime.time(): String = format(timeFormatter)
val dateFormatter = LocalDateTime.Format {
dayOfMonth(padding = Padding.ZERO)
char(' ')
monthName(MonthNames.ENGLISH_FULL)
}
plugins {
id("band.effective.office.client.kmp.feature")
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.epicarchitect:epic-calendar-compose:1.0.8")
}
}
}
compose.resources {
publicResClass = false
packageOfResClass = "band.effective.office.tablet.feature.main"
generateResClass = auto
}
\ Нет новой строки в конце файла
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="room_occupancy_date">Занято до ${time}</string>
<string name="room_occupancy_time">· До конца ${duration}</string>
<string name="free_room_occupancy_date">Свободна до ${time}</string>
<string name="free_room_occupancy_time">· До начала ${duration}</string>
<string name="stop_meeting_button">Освободить</string>
<string name="tv_property">TV</string>
<string name="usb_property">USB</string>
<string name="electric_socket_property_nominative">Розетка</string>
<string name="electric_socket_property_genitive">Розетки</string>
<string name="electric_socket_property_plural">Розеток</string>
<string name="hour_nominative">час</string>
<string name="hour_genitive">часа</string>
<string name="hour_plural">часов</string>
<string name="minuit_nominative">минута</string>
<string name="minuit_genitive">минуты</string>
<string name="minuit_plural">минут</string>
<string name="booking_view_title">Занять</string>
<string name="booking_button_text">Занять ${roomName}</string>
<string name="busy_duration_string">До конца</string>
<string name="see_free_room">Смотреть другие переговорки</string>
<string name="on_today_string">на сегодня</string>
<string name="start_date_time">${date} с ${time}</string>
<string name="current_length_string">${currentLength} мин</string>
<string name="list_organizers_title">Занятое время</string>
<string name="event_list_title">Занятое время</string>
<string name="no_select_organizer_alert">Выберите организатора</string>
<string name="no_correct_time">В это время будет другая бронь</string>
<string name="connection_lost">Нет подключения к интернету</string>
<string name="disconnect">Нет соединения с сервером</string>
<string name="reset">Перезагрузите приложение</string>
<string name="reset_button">Перезагрузить приложение</string>
<string name="exit">Выход</string>
<string name="rooms">Переговорки</string>
<string name="choose_room">Выбрать ${nameRoom}</string>
<string name="free_now">Сейчас свободна</string>
<string name="data_not_upd">Данные брони могут быть неактуальны</string>
<string name="fastbooking_button">${time} мин</string>
<string name="fastbooking_title">Занять любую переговорку на:</string>
<string name="empty_slot">Свободно ${freeTime} мин</string>
<string name="event_slot">Занято ${organizer}</string>
<string name="multislot">${size} брони</string>
<string name="cancel">Отмена</string>
<string name="loading_slot">Слот загружается</string>
<string name="loading_slot_for_time">Загрузка слота на время: ${start} - ${finish}</string>
</resources>
\ Нет новой строки в конце файла
package band.effective.office.tablet.feature.main
sealed interface Intent {
object OnOpenFreeRoomModal : Intent
object RebootRequest : Intent
data class OnChangeEventRequest(val eventInfo: Any) : Intent
data class OnSelectRoom(val index: Int) : Intent
object OnUpdate : Intent
data class OnFastBooking(val minDuration: Int) : Intent
data class OnUpdateSelectDate(val updateInDays: Int) : Intent
object OnResetSelectDate : Intent
}
\ Нет новой строки в конце файла
package band.effective.office.tablet.feature.main
sealed interface Label {
data class ShowToast(val text: String) : Label
}
package band.effective.office.tablet.feature.main
import com.arkivanov.decompose.ComponentContext
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.plus
@OptIn(ExperimentalTime::class)
class MainComponent(
private val componentContext: ComponentContext
) : ComponentContext by componentContext
\ Нет новой строки в конце файла
private val componentContext: ComponentContext,
val onSettings: () -> Unit
) : ComponentContext by componentContext {
private val componentScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val _state = MutableStateFlow(State.defaultState)
val state: StateFlow<State> = _state.asStateFlow()
private val _label = MutableSharedFlow<Label>()
val label: SharedFlow<Label> = _label.asSharedFlow()
init {
// Initialize component
componentScope.launch {
// Check if settings are empty, if so, show settings screen
if (shouldShowSettings()) {
_state.value = _state.value.copy(
isSettings = true
)
}
// Initial load of room information
loadRoomInfo()
}
}
private fun shouldShowSettings(): Boolean {
// In the original code, this checked if checkSettingsUseCase().isEmpty()
// For now, we'll return false to avoid showing settings screen
return false
}
private fun loadRoomInfo() {
componentScope.launch {
// Set loading state
_state.value = _state.value.copy(
isLoad = true,
isData = false,
isError = false
)
try {
// Simulate loading room info
// In the original code, this would call roomInfoUseCase()
withContext(Dispatchers.Default) {
// Simulate network delay
kotlinx.coroutines.delay(1000)
}
// Update state with loaded data
_state.value = _state.value.copy(
isLoad = false,
isData = true,
roomList = listOf(
"Room 1",
"Room 2",
"Room 3"
),
selectedDate = Clock.System.now(),
)
} catch (e: Exception) {
// Handle error
_state.value = _state.value.copy(
isLoad = false,
isError = true
)
// Show error toast
showToast("Error loading room information: ${e.message}")
}
}
}
private fun showToast(message: String) {
componentScope.launch {
_label.emit(Label.ShowToast(message))
}
}
private fun reboot(refresh: Boolean = false) {
componentScope.launch {
if (refresh) {
_state.value = _state.value.copy(
isLoad = true,
isData = false
)
}
// Reload room info
loadRoomInfo()
}
}
private fun updateRoomInfo(roomIndex: Int) {
// In the original code, this would update the selected room info
// For now, we'll just update the selected room index
_state.value = _state.value.copy(
indexSelectRoom = roomIndex
)
}
private fun updateDate(updateInDays: Int) {
// In the original code, this would update the selected date
// For now, we'll just update the selected date string
val newDate = if (updateInDays > 0) {
"Today + $updateInDays days"
} else if (updateInDays < 0) {
"Today - ${-updateInDays} days"
} else {
"Today"
}
_state.value = _state.value.copy(
selectedDate = Clock.System.now().plus(1, DateTimeUnit.HOUR),
)
}
// Intent handling
fun sendIntent(intent: Intent) {
when (intent) {
is Intent.OnOpenFreeRoomModal -> {
// In the original code, this would open a modal window
// For now, we'll just show a toast
showToast("Opening free room modal")
}
is Intent.RebootRequest -> {
reboot(refresh = true)
}
is Intent.OnChangeEventRequest -> {
// In the original code, this would open a modal window to change an event
// For now, we'll just show a toast
showToast("Opening change event modal")
}
is Intent.OnSelectRoom -> {
updateRoomInfo(intent.index)
}
Intent.OnUpdate -> {
reboot(refresh = false)
}
is Intent.OnFastBooking -> {
// In the original code, this would open a modal window for fast booking
// For now, we'll just show a toast
showToast("Opening fast booking modal for ${intent.minDuration} minutes")
}
is Intent.OnUpdateSelectDate -> {
updateDate(intent.updateInDays)
}
Intent.OnResetSelectDate -> {
updateDate(0)
}
}
}
}
package band.effective.office.tablet.feature.main
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlin.time.ExperimentalTime
@OptIn(ExperimentalTime::class)
@Composable
fun MainScreen(component: MainComponent) {
val state by component.state.collectAsState()
}
\ Нет новой строки в конце файла
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
when {
state.isError -> {
Text("Error occurred")
Button(onClick = { component.sendIntent(Intent.RebootRequest) }) {
Text("Retry")
}
}
state.isLoad -> {
Text("Loading...")
}
state.isData -> {
MainScreenView(
isDisconnect = state.isDisconnect,
roomList = state.roomList,
indexSelectRoom = state.indexSelectRoom,
timeToNextEvent = state.timeToNextEvent,
onRoomButtonClick = { component.sendIntent(Intent.OnSelectRoom(it)) },
onCancelEventRequest = { component.sendIntent(Intent.OnOpenFreeRoomModal) },
onFastBooking = { component.sendIntent(Intent.OnFastBooking(it)) },
onUpdate = { component.sendIntent(Intent.OnUpdate) },
onIncrementData = { component.sendIntent(Intent.OnUpdateSelectDate(updateInDays = 1)) },
onDecrementData = { component.sendIntent(Intent.OnUpdateSelectDate(updateInDays = -1)) },
selectedDate = state.selectedDate,
onResetDate = { component.sendIntent(Intent.OnResetSelectDate) }
)
}
state.isSettings -> {
component.onSettings()
}
}
}
}
package band.effective.office.tablet.feature.main
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.unit.dp
import band.effective.office.tablet.core.ui.date.DatePickerView
import band.effective.office.tablet.core.ui.date.DateTimeView
import epicarchitect.calendar.compose.datepicker.EpicDatePicker
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
import org.jetbrains.compose.resources.stringResource
@OptIn(ExperimentalTime::class)
@Composable
fun MainScreenView(
isDisconnect: Boolean,
roomList: List<Any>,
indexSelectRoom: Int,
timeToNextEvent: Int,
onRoomButtonClick: (Int) -> Unit,
onCancelEventRequest: () -> Unit,
onFastBooking: (Int) -> Unit,
onUpdate: () -> Unit,
onIncrementData: () -> Unit,
onDecrementData: () -> Unit,
selectedDate: Instant,
onResetDate: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize().background(color = MaterialTheme.colorScheme.background)) {
Row(modifier = Modifier.fillMaxSize()) {
// Left side - Room info and calendar
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(0.6f)
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(bottom = 30.dp)
) {
item {
DateTimeView(
modifier = Modifier.padding(
start = 30.dp,
top = 50.dp,
end = 20.dp,
bottom = 0.dp
).height(70.dp),
selectDate = selectedDate,
increment = onIncrementData,
decrement = onDecrementData,
onOpenDateTimePickerModal = { },
currentDate = Clock.System.now(),
back = onResetDate,
)
}
item {
// Room info
RoomInfo(
roomName = if (roomList.isNotEmpty() && indexSelectRoom < roomList.size)
"Room ${indexSelectRoom + 1}" else "No room selected",
timeToNextEvent = timeToNextEvent,
onCancelEvent = onCancelEventRequest,
isDisconnect = isDisconnect
)
}
// Here would be the slots, but we don't have SlotComponent in the new implementation
item {
Text(
"Events would be displayed here",
modifier = Modifier.padding(16.dp)
)
}
}
// Disconnect indicator
if (isDisconnect) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f)),
contentAlignment = Alignment.Center
) {
Text(
"Disconnected",
color = Color.White
)
}
}
}
// Right side - Fast booking buttons and room list
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
// Fast booking buttons
FastBookingButtons(onBooking = onFastBooking)
// Room list
RoomList(
count = roomList.size,
indexSelectRoom = indexSelectRoom,
onClick = onRoomButtonClick
)
}
}
}
}
@Composable
fun RoomInfo(
roomName: String,
timeToNextEvent: Int,
onCancelEvent: () -> Unit,
isDisconnect: Boolean
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
roomName,
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
"Time to next event: $timeToNextEvent minutes",
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(16.dp))
if (timeToNextEvent > 0) {
Button(onClick = onCancelEvent) {
Text("Cancel Event")
}
}
}
}
@Composable
fun FastBookingButtons(onBooking: (Int) -> Unit) {
Column {
Text(
text = stringResource(Res.string.fastbooking_title),
style = MaterialTheme.typography.headlineMedium
)
Spacer(Modifier.height(10.dp))
Row {
val buttonModifier = Modifier.fillMaxWidth().weight(1f)
listOf(15, 30, 60).forEachIndexed { index, time ->
if (index != 0) {
Spacer(Modifier.width(10.dp))
}
Button(
modifier = buttonModifier,
onClick = { onBooking(time) }
) {
Text(stringResource(Res.string.fastbooking_button, time))
}
}
}
}
}
@Composable
fun RoomList(count: Int, indexSelectRoom: Int, onClick: (Int) -> Unit) {
Column(modifier = Modifier.fillMaxWidth()) {
for (i in 0 until count) {
RoomButton(
modifier = Modifier
.background(
color = if (i == indexSelectRoom) MaterialTheme.colorScheme.surface else Color.Transparent,
shape = CircleShape
)
.clickable { onClick(i) }
.fillMaxWidth()
.clip(CircleShape)
.padding(10.dp),
roomName = "Room ${i + 1}",
isOccupied = false
)
Spacer(Modifier.height(5.dp))
}
}
}
@Composable
fun RoomButton(modifier: Modifier, roomName: String, isOccupied: Boolean) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.height(10.dp)
.width(10.dp)
.background(
color = if (isOccupied) Color.Red else Color.Green,
shape = CircleShape
)
)
Spacer(Modifier.width(10.dp))
Text(
text = roomName,
style = MaterialTheme.typography.bodyLarge
)
}
}
}
\ Нет новой строки в конце файла
package band.effective.office.tablet.feature.main
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
data class State @OptIn(ExperimentalTime::class) constructor(
val isLoad: Boolean,
val isData: Boolean,
val isError: Boolean,
val isDisconnect: Boolean,
val updatedEvent: Any,
val isSettings: Boolean,
val roomList: List<Any>,
val indexSelectRoom: Int,
val timeToNextEvent: Int,
val selectedDate: Instant,
) {
companion object {
@OptIn(ExperimentalTime::class)
val defaultState =
State(
isLoad = true,
isData = false,
isError = false,
isDisconnect = false,
isSettings = false,
updatedEvent = Any(),
roomList = listOf(),
indexSelectRoom = 0,
timeToNextEvent = 0,
selectedDate = Clock.System.now(),
)
}
}
\ Нет новой строки в конце файла
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать