shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/screens/SpeakerDetailScreen.kt (153 lines of code) (raw):

package org.jetbrains.kotlinconf.screens import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio 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.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.jetbrains.compose.resources.stringResource import org.jetbrains.kotlinconf.ScrollToTopHandler import org.jetbrains.kotlinconf.SessionId import org.jetbrains.kotlinconf.SpeakerId import org.jetbrains.kotlinconf.generated.resources.Res import org.jetbrains.kotlinconf.generated.resources.schedule_in_x_minutes import org.jetbrains.kotlinconf.generated.resources.speaker_detail_error_not_found import org.jetbrains.kotlinconf.generated.resources.speaker_detail_title import org.jetbrains.kotlinconf.toEmotion import org.jetbrains.kotlinconf.ui.components.Divider import org.jetbrains.kotlinconf.ui.components.MainHeaderTitleBar import org.jetbrains.kotlinconf.ui.components.MajorError import org.jetbrains.kotlinconf.ui.components.SpeakerAvatar import org.jetbrains.kotlinconf.ui.components.TalkCard import org.jetbrains.kotlinconf.ui.components.TalkStatus import org.jetbrains.kotlinconf.ui.components.Text import org.jetbrains.kotlinconf.ui.components.TopMenuButton import org.jetbrains.kotlinconf.ui.generated.resources.UiRes import org.jetbrains.kotlinconf.ui.generated.resources.arrow_left_24 import org.jetbrains.kotlinconf.ui.generated.resources.main_header_back import org.jetbrains.kotlinconf.ui.theme.KotlinConfTheme import org.jetbrains.kotlinconf.utils.FadingAnimationSpec import org.jetbrains.kotlinconf.utils.bottomInsetPadding import org.jetbrains.kotlinconf.utils.topInsetPadding import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @Composable fun SpeakerDetailScreen( speakerId: SpeakerId, onBack: () -> Unit, onSession: (SessionId) -> Unit, viewModel: SpeakerDetailViewModel = koinViewModel { parametersOf(speakerId) } ) { val speaker = viewModel.speaker.collectAsStateWithLifecycle().value val sessions = viewModel.sessions.collectAsStateWithLifecycle().value Column( Modifier .fillMaxSize() .background(color = KotlinConfTheme.colors.mainBackground) .padding(topInsetPadding()) ) { MainHeaderTitleBar( title = stringResource(Res.string.speaker_detail_title), startContent = { TopMenuButton( icon = UiRes.drawable.arrow_left_24, contentDescription = stringResource(UiRes.string.main_header_back), onClick = onBack, ) } ) Divider(thickness = 1.dp, color = KotlinConfTheme.colors.strokePale) AnimatedContent( targetState = speaker, contentKey = { speaker != null }, transitionSpec = { FadingAnimationSpec }, modifier = Modifier.fillMaxSize().weight(1f) ) { currentSpeaker -> if (currentSpeaker == null) { MajorError( message = stringResource(Res.string.speaker_detail_error_not_found), modifier = Modifier.fillMaxSize(), ) } else { val scrollState = rememberScrollState() ScrollToTopHandler(scrollState) Column( modifier = Modifier .verticalScroll(scrollState) .padding(vertical = 16.dp, horizontal = 12.dp) .padding(bottomInsetPadding()), ) { Text( text = currentSpeaker.name, style = KotlinConfTheme.typography.h2, color = KotlinConfTheme.colors.primaryText, selectable = true, modifier = Modifier.semantics { heading() } ) Spacer(Modifier.height(4.dp)) Text( text = currentSpeaker.position, style = KotlinConfTheme.typography.text2, color = KotlinConfTheme.colors.secondaryText, selectable = true, ) Spacer(Modifier.height(16.dp)) SpeakerAvatar( photoUrl = currentSpeaker.photoUrl, modifier = Modifier.widthIn(max = 300.dp) .aspectRatio(1f) ) Spacer(Modifier.height(24.dp)) Text( text = currentSpeaker.description, style = KotlinConfTheme.typography.text2, color = KotlinConfTheme.colors.longText, selectable = true, ) Spacer(Modifier.height(16.dp)) sessions.forEach { session -> TalkCard( title = session.title, titleHighlights = emptyList(), bookmarked = session.isFavorite, onBookmark = { isBookmarked -> viewModel.onBookmark(session.id, isBookmarked) }, tags = session.tags, tagHighlights = emptyList(), speakers = session.speakerLine, speakerHighlights = emptyList(), location = session.locationLine, lightning = session.isLightning, time = session.fullTimeline, timeNote = session.startsInMinutes?.let { count -> stringResource(Res.string.schedule_in_x_minutes, count) }, status = TalkStatus.Upcoming, initialEmotion = session.vote?.toEmotion(), feedbackEnabled = false, // Feedback not enabled on this screen userSignedIn = false, // Feedback not enabled on this screen onSubmitFeedback = { }, // Feedback not enabled on this screen onSubmitFeedbackWithComment = { _, _ -> }, // Feedback not enabled on this screen onRequestFeedbackWithComment = null, // Feedback not enabled on this screen onClick = { onSession(session.id) }, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), ) } } } } } }