fun ScheduleScreen()

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() },
                    )
                }
            }
        }
    }
}