From 310f6dede781d7e4ae4e1b77a6232bb4985e2e57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:39:22 +0000 Subject: [PATCH 1/3] Display speaker talks in speaker detail screen Agent-Logs-Url: https://github.com/paug/AndroidMakersApp/sessions/d0e10687-71d2-4d05-9059-b43c6a78a48c Co-authored-by: benju69 <2486590+benju69@users.noreply.github.com> --- .../composeResources/values/strings.xml | 1 + .../com/androidmakers/di/ViewModelModule.kt | 2 +- .../ui/common/navigation/AVALayout.kt | 1 + .../ui/speakers/SpeakerDetailViewModel.kt | 28 ++++++++----- .../ui/speakers/SpeakerDetailsScreen.kt | 41 +++++++++++++++++++ 5 files changed, 62 insertions(+), 11 deletions(-) diff --git a/shared/ui/src/commonMain/composeResources/values/strings.xml b/shared/ui/src/commonMain/composeResources/values/strings.xml index fc875354..8be1f703 100644 --- a/shared/ui/src/commonMain/composeResources/values/strings.xml +++ b/shared/ui/src/commonMain/composeResources/values/strings.xml @@ -75,6 +75,7 @@ Apply for App-Clinic Talk Details + Talks About this talk Speaker diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/di/ViewModelModule.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/di/ViewModelModule.kt index 94e105b4..015e82b4 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/di/ViewModelModule.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/di/ViewModelModule.kt @@ -16,7 +16,7 @@ val viewModelModule = module { viewModelOf(::SpeakerListViewModel) viewModelOf(::SponsorsViewModel) viewModelOf(::VenueViewModel) - viewModel { (speakerId: String) -> SpeakerDetailsViewModel(speakerId, get()) } + viewModel { (speakerId: String) -> SpeakerDetailsViewModel(speakerId, get(), get()) } viewModelOf(::AgendaViewModel) viewModel { (sessionId: String) -> SessionDetailViewModel(sessionId, get(), get(), get(), get(), get(), get(), get()) diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt index f3ca4fa1..7b5a9121 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/common/navigation/AVALayout.kt @@ -410,6 +410,7 @@ private fun AVANavDisplay( SpeakerDetailsRoute( speakerDetailsViewModel = koinViewModel(key = key.speakerId) { parametersOf(key.speakerId) }, onBackClick = { navigator.goBack() }, + onSessionClick = { sessionId -> navigator.navigateToSessionDetail(sessionId) }, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = LocalNavAnimatedContentScope.current, ) diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailViewModel.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailViewModel.kt index d59300bc..0305bb43 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailViewModel.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailViewModel.kt @@ -4,28 +4,35 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.androidmakers.ui.model.Lce import com.androidmakers.ui.model.toLce +import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.SocialsItem import fr.androidmakers.domain.model.Speaker +import fr.androidmakers.domain.repo.SessionsRepository import fr.androidmakers.domain.repo.SpeakersRepository import fr.androidmakers.domain.utils.UrlOpener import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn class SpeakerDetailsViewModel( speakerId: String, speakersRepository: SpeakersRepository, + sessionsRepository: SessionsRepository, ) : ViewModel() { - val uiState: StateFlow> = speakersRepository - .getSpeaker(speakerId).map { result -> - result.map { - SpeakerDetailsUiState( - speaker = it - ) - }.toLce() - } + val uiState: StateFlow> = combine( + speakersRepository.getSpeaker(speakerId), + sessionsRepository.getSessions(refresh = false), + ) { speakerResult, sessionsResult -> + speakerResult.map { speaker -> + SpeakerDetailsUiState( + speaker = speaker, + sessions = sessionsResult.getOrDefault(emptyList()) + .filter { session -> speakerId in session.speakers } + ) + }.toLce() + } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), @@ -38,5 +45,6 @@ class SpeakerDetailsViewModel( } data class SpeakerDetailsUiState( - val speaker: Speaker + val speaker: Speaker, + val sessions: List = emptyList(), ) diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt index a812cc9d..14b16a02 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt @@ -5,12 +5,14 @@ import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -39,11 +41,14 @@ import com.androidmakers.ui.common.SocialButtons import com.androidmakers.ui.common.toUrlOpener import com.androidmakers.ui.model.Lce import com.androidmakers.ui.theme.LocalIsNeobrutalism +import com.androidmakers.ui.theme.neoBrutalElevation +import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.SocialsItem import fr.paug.androidmakers.ui.Res import fr.paug.androidmakers.ui.ic_arrow_back import fr.paug.androidmakers.ui.back import fr.paug.androidmakers.ui.speakers +import fr.paug.androidmakers.ui.talks import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -52,6 +57,7 @@ import org.jetbrains.compose.resources.stringResource fun SpeakerDetailsRoute( speakerDetailsViewModel: SpeakerDetailsViewModel, onBackClick: () -> Unit, + onSessionClick: (sessionId: String) -> Unit = {}, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { @@ -68,6 +74,7 @@ fun SpeakerDetailsRoute( uiState = state.content, onSocialItemClick = { speakerDetailsViewModel.openSpeakerLink(urlOpener, it) }, onBackClick = onBackClick, + onSessionClick = onSessionClick, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, ) @@ -81,6 +88,7 @@ fun SpeakerDetailsScreen( uiState: SpeakerDetailsUiState, onSocialItemClick: (SocialsItem) -> Unit, onBackClick: () -> Unit, + onSessionClick: (sessionId: String) -> Unit = {}, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { @@ -162,6 +170,39 @@ fun SpeakerDetailsScreen( speaker = speaker, onClickOnItem = onSocialItemClick ) + + if (uiState.sessions.isNotEmpty()) { + SpeakerTalksSection( + sessions = uiState.sessions, + onSessionClick = onSessionClick, + ) + } + } + } +} + +@Composable +private fun SpeakerTalksSection( + sessions: List, + onSessionClick: (sessionId: String) -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = stringResource(Res.string.talks), + style = MaterialTheme.typography.titleMedium, + ) + for (session in sessions) { + ElevatedCard( + modifier = Modifier.fillMaxWidth().neoBrutalElevation(), + onClick = { onSessionClick(session.id) }, + shape = MaterialTheme.shapes.large, + ) { + Text( + modifier = Modifier.padding(16.dp), + text = session.title, + style = MaterialTheme.typography.bodyLarge, + ) + } } } } From 0fa3b8fbb11335c7a2358ff279842d3f37d4a406 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:39:58 +0000 Subject: [PATCH 2/3] Use forEach instead of for loop in Compose composable Agent-Logs-Url: https://github.com/paug/AndroidMakersApp/sessions/d0e10687-71d2-4d05-9059-b43c6a78a48c Co-authored-by: benju69 <2486590+benju69@users.noreply.github.com> --- .../com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt index 14b16a02..d4a886d2 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt @@ -191,7 +191,7 @@ private fun SpeakerTalksSection( text = stringResource(Res.string.talks), style = MaterialTheme.typography.titleMedium, ) - for (session in sessions) { + sessions.forEach { session -> ElevatedCard( modifier = Modifier.fillMaxWidth().neoBrutalElevation(), onClick = { onSessionClick(session.id) }, From 2971f57a1dacd6135459461dd6ec9aee6ade2909 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:47:48 +0000 Subject: [PATCH 3/3] Add Compose previews to SpeakerDetailsScreen Agent-Logs-Url: https://github.com/paug/AndroidMakersApp/sessions/8ad1b52f-988f-42e0-8afb-91a6dfad042d Co-authored-by: benju69 <2486590+benju69@users.noreply.github.com> --- .../ui/speakers/SpeakerDetailsScreen.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt index d4a886d2..f60869ba 100644 --- a/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt +++ b/shared/ui/src/commonMain/kotlin/com/androidmakers/ui/speakers/SpeakerDetailsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.style.LineBreak import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -44,11 +45,13 @@ import com.androidmakers.ui.theme.LocalIsNeobrutalism import com.androidmakers.ui.theme.neoBrutalElevation import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.SocialsItem +import fr.androidmakers.domain.model.Speaker import fr.paug.androidmakers.ui.Res import fr.paug.androidmakers.ui.ic_arrow_back import fr.paug.androidmakers.ui.back import fr.paug.androidmakers.ui.speakers import fr.paug.androidmakers.ui.talks +import kotlinx.datetime.LocalDateTime import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -206,3 +209,69 @@ private fun SpeakerTalksSection( } } } + +// region Previews + +private val previewSpeaker = Speaker( + id = "speaker-1", + name = "Ada Lovelace", + company = "Babbage & Co.", + bio = "Mathematician and writer, chiefly known for her work on Charles Babbage's proposed mechanical general-purpose computer, the Analytical Engine.", + photoUrl = null, + socials = listOf( + SocialsItem(name = "Twitter", url = "https://twitter.com/ada"), + ), +) + +private val previewSessions = listOf( + Session( + id = "session-1", + title = "The First Algorithm: A Deep Dive into Analytical Engine Programming", + speakers = listOf("speaker-1"), + startsAt = LocalDateTime(2026, 4, 9, 9, 0), + endsAt = LocalDateTime(2026, 4, 9, 9, 45), + roomId = "room-1", + isServiceSession = false, + type = "talk", + ), + Session( + id = "session-2", + title = "Beyond Numbers: Mathematical Imagination in the 19th Century", + speakers = listOf("speaker-1"), + startsAt = LocalDateTime(2026, 4, 10, 11, 0), + endsAt = LocalDateTime(2026, 4, 10, 11, 45), + roomId = "room-2", + isServiceSession = false, + type = "talk", + ), +) + +@Preview +@Composable +private fun SpeakerDetailsScreenPreview() { + SpeakerDetailsScreen( + uiState = SpeakerDetailsUiState( + speaker = previewSpeaker, + sessions = previewSessions, + ), + onSocialItemClick = {}, + onBackClick = {}, + onSessionClick = {}, + ) +} + +@Preview +@Composable +private fun SpeakerDetailsScreenNoTalksPreview() { + SpeakerDetailsScreen( + uiState = SpeakerDetailsUiState( + speaker = previewSpeaker, + sessions = emptyList(), + ), + onSocialItemClick = {}, + onBackClick = {}, + onSessionClick = {}, + ) +} + +// endregion