package agzam.vyatsu.ui.picker import agzam.vyatsu.domain.model.Group import agzam.vyatsu.domain.model.Teacher 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.WindowInsets 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.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.material3.TextButton 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.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @OptIn(ExperimentalMaterial3Api::class) @Composable fun PickerScreen( viewModel: PickerViewModel, onGroupPicked: (Group) -> Unit, onTeacherPicked: (Teacher) -> Unit ) { val state by viewModel.uiState.collectAsState() var segment by rememberSaveable { mutableStateOf(PickerSegment.GROUPS) } LaunchedEffect(segment) { viewModel.selectSegment(segment) } Scaffold { innerPadding -> Column( Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars) .padding(innerPadding) .padding(horizontal = 16.dp, vertical = 8.dp) ) { SingleChoiceSegmentedButtonRow(Modifier.fillMaxWidth()) { SegmentedButton( selected = segment == PickerSegment.GROUPS, onClick = { segment = PickerSegment.GROUPS }, shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2) ) { Text("Группы") } SegmentedButton( selected = segment == PickerSegment.TEACHERS, onClick = { segment = PickerSegment.TEACHERS }, shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2) ) { Text("Преподаватели") } } Spacer(Modifier.height(16.dp)) when (segment) { PickerSegment.GROUPS -> GroupSection( state = state, onSearch = viewModel::searchGroup, onRetry = viewModel::retryGroups, onRefresh = viewModel::refreshGroups, onGroupPicked = onGroupPicked ) PickerSegment.TEACHERS -> TeacherSection( state = state, onSearch = viewModel::searchTeacher, onRetry = viewModel::retryTeachers, onTeacherPicked = onTeacherPicked ) } } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun GroupSection( state: PickerUiState, onSearch: (String) -> Unit, onRetry: () -> Unit, onRefresh: () -> Unit, onGroupPicked: (Group) -> Unit ) { var isSearchActive by remember { mutableStateOf(false) } if (!isSearchActive) { Row( Modifier .fillMaxWidth() .padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = "Выберите группу", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.weight(1f) ) IconButton(onClick = onRefresh) { Icon(Icons.Default.Refresh, contentDescription = "Обновить") } } } if (state.groupsLoading && state.groups.isEmpty()) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { CircularProgressIndicator() } return } if (state.error != null && state.groups.isEmpty()) { Column(Modifier.fillMaxWidth()) { Text( state.error, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error ) TextButton(onClick = onRetry) { Text("Повторить") } } return } SearchBar( modifier = Modifier.fillMaxWidth(), expanded = isSearchActive, onExpandedChange = { isSearchActive = it }, inputField = { SearchBarDefaults.InputField( query = state.groupQuery, onQueryChange = onSearch, onSearch = { isSearchActive = false }, expanded = isSearchActive, onExpandedChange = { isSearchActive = it }, placeholder = { Text("Поиск группы") }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, trailingIcon = { if (isSearchActive && state.groupQuery.isNotEmpty()) { IconButton(onClick = { onSearch("") }) { Icon(Icons.Default.Clear, contentDescription = "Очистить") } } } ) } ) { GroupList( groups = state.filteredGroups, onPick = { group -> isSearchActive = false onGroupPicked(group) } ) } if (!isSearchActive) { Spacer(modifier = Modifier.height(8.dp)) GroupList(state.filteredGroups, onGroupPicked) } } @Composable private fun GroupList(groups: List, onPick: (Group) -> Unit) { LazyColumn(modifier = Modifier.fillMaxSize()) { items(groups) { group -> Column(modifier = Modifier.fillMaxWidth().clickable { onPick(group) }) { Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), contentAlignment = Alignment.CenterStart ) { Text(group.name, style = MaterialTheme.typography.bodyMedium) } } HorizontalDivider(thickness = 1.dp, color = MaterialTheme.colorScheme.outlineVariant) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TeacherSection( state: PickerUiState, onSearch: (String) -> Unit, onRetry: () -> Unit, onTeacherPicked: (Teacher) -> Unit ) { var isSearchActive by remember { mutableStateOf(false) } val progress = state.syncProgress val syncing = progress != null && progress.done < progress.total if (!isSearchActive) { Row( Modifier .fillMaxWidth() .padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = "Выберите преподавателя", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.weight(1f) ) IconButton(onClick = onRetry) { Icon(Icons.Default.Refresh, contentDescription = "Обновить") } } } if (syncing) { Column(Modifier.fillMaxWidth().padding(vertical = 8.dp)) { LinearProgressIndicator( progress = { progress.done / progress.total.toFloat() }, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(4.dp)) Text( text = "Кафедры: ${progress.done}/${progress.total}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } if (state.teachersLoading && state.teachers.isEmpty() && !syncing) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { CircularProgressIndicator() } return } if (state.error != null && state.teachers.isEmpty() && !syncing) { Column(Modifier.fillMaxWidth()) { Text( state.error, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error ) TextButton(onClick = onRetry) { Text("Повторить") } } return } SearchBar( modifier = Modifier.fillMaxWidth(), expanded = isSearchActive, onExpandedChange = { isSearchActive = it }, inputField = { SearchBarDefaults.InputField( query = state.teacherQuery, onQueryChange = onSearch, onSearch = { isSearchActive = false }, expanded = isSearchActive, onExpandedChange = { isSearchActive = it }, placeholder = { Text("Поиск преподавателя") }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, trailingIcon = { if (isSearchActive && state.teacherQuery.isNotEmpty()) { IconButton(onClick = { onSearch("") }) { Icon(Icons.Default.Clear, contentDescription = "Очистить") } } } ) } ) { TeacherList( teachers = state.filteredTeachers, onPick = { teacher -> isSearchActive = false onTeacherPicked(teacher) } ) } if (!isSearchActive) { Spacer(modifier = Modifier.height(8.dp)) when { state.teachers.isEmpty() -> Text( text = "Кафедры без расписания на эту неделю", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) else -> TeacherList(state.filteredTeachers, onTeacherPicked) } } } @Composable private fun TeacherList(teachers: List, onPick: (Teacher) -> Unit) { LazyColumn(modifier = Modifier.fillMaxSize()) { items(teachers) { teacher -> Column(modifier = Modifier.fillMaxWidth().clickable { onPick(teacher) }) { Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), contentAlignment = Alignment.CenterStart ) { Text(teacher.name, style = MaterialTheme.typography.bodyMedium) } } HorizontalDivider(thickness = 1.dp, color = MaterialTheme.colorScheme.outlineVariant) } } }