in shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/screens/ScheduleScreen.kt [101:272]
fun ScheduleScreen(
onSession: (SessionId) -> Unit,
onPrivacyNoticeNeeded: () -> Unit,
onRequestFeedbackWithComment: (SessionId) -> Unit,
viewModel: ScheduleViewModel = koinViewModel(),
) {
val scope = rememberCoroutineScope()
var bookmarkFilterEnabled by rememberSaveable { mutableStateOf(false) }
var searchQuery by rememberSaveable { mutableStateOf("") }
val listState = rememberLazyListState()
val state = viewModel.uiState.collectAsStateWithLifecycle().value
val shouldNavigateToPrivacyNotice by viewModel.navigateToPrivacyNotice.collectAsStateWithLifecycle()
LaunchedEffect(shouldNavigateToPrivacyNotice) {
if (shouldNavigateToPrivacyNotice) {
onPrivacyNoticeNeeded()
viewModel.onNavigatedToPrivacyNotice()
}
}
var headerState by rememberSaveable { mutableStateOf(MainHeaderContainerState.Title) }
val isSearch = rememberSaveable(headerState) { headerState == MainHeaderContainerState.Search }
var firstScrollPerformed by rememberSaveable(isSearch, searchQuery) { mutableStateOf(false) }
if (!firstScrollPerformed) {
if (isSearch) {
LaunchedEffect(searchQuery) {
if (listState.firstVisibleItemIndex > 1) {
listState.scrollToItem(0)
} else {
listState.animateScrollToItem(0)
}
firstScrollPerformed = true
}
} else {
LaunchedEffect(state) {
if (state is ScheduleUiState.Content && state.firstActiveIndex != -1) {
listState.scrollToItem(state.firstActiveIndex)
firstScrollPerformed = true
}
}
}
}
val params = ScheduleSearchParams(
searchQuery = searchQuery,
isSearch = isSearch,
isBookmarkedOnly = bookmarkFilterEnabled,
)
LaunchedEffect(params) {
viewModel.setSearchParams(params)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(color = KotlinConfTheme.colors.mainBackground)
) {
Header(
startContent = { NowButtonContent(state, listState) },
headerState = headerState,
onHeaderStateChange = { headerState = it },
bookmarkFilterEnabled = bookmarkFilterEnabled,
onBookmarkFilter = { bookmarkFilterEnabled = it },
searchQuery = searchQuery,
onSearchQueryChange = { searchQuery = it },
onClearSearch = { viewModel.resetFilters() },
viewModel = viewModel
)
Divider(
thickness = 1.dp,
color = KotlinConfTheme.colors.strokePale,
)
AnimatedContent(
targetState = state,
modifier = Modifier.fillMaxSize().clipToBounds(),
contentKey = {
when (state) {
is ScheduleUiState.Content -> 1
ScheduleUiState.Error, ScheduleUiState.Loading -> 2
}
},
transitionSpec = { FadingAnimationSpec },
contentAlignment = Alignment.Center,
) { targetState ->
when (targetState) {
is ScheduleUiState.Content -> {
val days = targetState.days
val items = targetState.items
Column(Modifier.fillMaxSize()) {
AnimatedVisibility(!isSearch) {
// Day switcher selection state calculated from the scroll state
val conferenceDates = days.map { it.date }
val computedDayIndex by derivedStateOf {
items.asSequence()
.take(listState.firstVisibleItemIndex + 1)
.findLast { it is DayHeaderItem }
?.let {
val visibleDate = (it as DayHeaderItem).value.date
conferenceDates.indexOf(visibleDate)
} ?: 0
}
// Override for the day switcher selection
var targetDayIndex by remember { mutableStateOf<Int?>(null) }
val selectedDayIndex = targetDayIndex ?: computedDayIndex
Switcher(
items = remember(conferenceDates) {
conferenceDates.map { DateTimeFormatting.date(it) }
},
selectedIndex = selectedDayIndex,
onSelect = { index ->
scope.launch {
val dayItemIndex = items.indexOf(DayHeaderItem(days[index]))
// Temporarily override the scroll state based selection
targetDayIndex = index
// Scroll to the item
listState.animateScrollToItem(dayItemIndex)
// Remove override, let scroll state determine the selection
targetDayIndex = null
}
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp)
)
}
val tags by viewModel.filterItems.collectAsStateWithLifecycle()
ScheduleList(
scheduleItems = items,
onSession = onSession,
listState = listState,
userSignedIn = targetState.userSignedIn,
isSearch = isSearch,
onSubmitFeedback = { sessionId, emotion ->
viewModel.onSubmitFeedback(sessionId, emotion)
},
onSubmitFeedbackWithComment = { sessionId, emotion, comment ->
viewModel.onSubmitFeedbackWithComment(sessionId, emotion, comment)
},
onRequestFeedbackWithComment = if (LocalFlags.current.redirectFeedbackToSessionPage) {
onRequestFeedbackWithComment
} else {
null
},
onBookmark = { sessionId, isBookmarked ->
viewModel.onBookmark(sessionId, isBookmarked)
},
filterItems = tags,
onToggleFilter = { item, selected -> viewModel.toggleFilter(item, selected) },
modifier = Modifier.fillMaxSize().clipToBounds()
)
}
}
ScheduleUiState.Error, ScheduleUiState.Loading -> {
NormalErrorWithLoading(
message = stringResource(Res.string.schedule_error_no_data),
isLoading = targetState is ScheduleUiState.Loading,
modifier = Modifier.fillMaxSize(),
onRetry = { viewModel.refresh() },
)
}
}
}
}
}