From 821d903836d23effe8830f16f646898a88cc7406 Mon Sep 17 00:00:00 2001 From: Gerard Paligot Date: Fri, 10 Nov 2023 21:35:24 +0100 Subject: [PATCH] refactor(speakers-feature): rework experience for landscape mode. --- settings.gradle.kts | 1 + .../devfest/android/theme/MainMobile.kt | 4 +- .../devfest/android/theme/m3/main/Home.kt | 4 +- .../speakers-feature/build.gradle.kts | 5 +- .../feature/SpeakerDetailHorizontal.kt | 93 ------------------- .../feature/SpeakerDetailOrientable.kt | 44 --------- .../feature/SpeakerDetailOrientableVM.kt | 65 +++++++++++++ .../m3/speakers/feature/SpeakerDetailVM.kt | 51 ---------- .../speakers/feature/SpeakerDetailVertical.kt | 88 ------------------ ...sListVM.kt => SpeakersListOrientableVM.kt} | 3 +- theme-m3/speakers/speakers-screens/.gitignore | 1 + .../speakers-screens/build.gradle.kts | 24 +++++ .../speakers/screens/SpeakerAvatarScreen.kt | 39 ++++++++ .../screens/SpeakerDetailOrientable.kt | 61 ++++++++++++ .../screens/SpeakerDetailVerticalScreen.kt | 80 ++++++++++++++++ .../screens}/SpeakerListOrientable.kt | 9 +- .../speakers/screens/SpeakersListScreen.kt} | 6 +- .../m3/speakers/ui/SpeakerDetailSection.kt | 69 ++------------ 18 files changed, 295 insertions(+), 352 deletions(-) delete mode 100644 theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailHorizontal.kt delete mode 100644 theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientable.kt create mode 100644 theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt delete mode 100644 theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVM.kt delete mode 100644 theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVertical.kt rename theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/{SpeakersListVM.kt => SpeakersListOrientableVM.kt} (92%) create mode 100644 theme-m3/speakers/speakers-screens/.gitignore create mode 100644 theme-m3/speakers/speakers-screens/build.gradle.kts create mode 100644 theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerAvatarScreen.kt create mode 100644 theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailOrientable.kt create mode 100644 theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailVerticalScreen.kt rename theme-m3/speakers/{speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature => speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens}/SpeakerListOrientable.kt (85%) rename theme-m3/speakers/{speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersList.kt => speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakersListScreen.kt} (95%) diff --git a/settings.gradle.kts b/settings.gradle.kts index 76ce0333c..3fad45c71 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,7 @@ include(":theme-m3:main:mobile") include(":theme-m3:schedules:schedules-ui") include(":theme-m3:schedules:schedules-feature") include(":theme-m3:speakers:speakers-ui") +include(":theme-m3:speakers:speakers-screens") include(":theme-m3:speakers:speakers-feature") include(":theme-m3:networking:networking-ui") include(":theme-m3:networking:networking-feature") diff --git a/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/MainMobile.kt b/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/MainMobile.kt index 47ef6763c..857895266 100644 --- a/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/MainMobile.kt +++ b/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/MainMobile.kt @@ -22,7 +22,7 @@ import org.gdglille.devfest.android.theme.m3.networking.feature.VCardQrCodeScann import org.gdglille.devfest.android.theme.m3.partners.feature.PartnerDetailOrientableVM import org.gdglille.devfest.android.theme.m3.schedules.feature.AgendaFiltersVM import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleDetailVM -import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakerDetailVM +import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakerDetailOrientableVM import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme import org.gdglille.devfest.models.ui.ExportNetworkingUi import org.gdglille.devfest.repositories.AgendaRepository @@ -163,7 +163,7 @@ fun MainMobile( route = "speakers/{speakerId}", arguments = listOf(navArgument("speakerId") { type = NavType.StringType }) ) { - SpeakerDetailVM( + SpeakerDetailOrientableVM( speakerId = it.arguments?.getString("speakerId")!!, agendaRepository = agendaRepository, alarmScheduler = alarmScheduler, diff --git a/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/Home.kt b/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/Home.kt index a3d9d50ee..604f59e38 100644 --- a/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/Home.kt +++ b/theme-m3/main/mobile/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/Home.kt @@ -24,7 +24,7 @@ import org.gdglille.devfest.android.theme.m3.navigation.Screen import org.gdglille.devfest.android.theme.m3.networking.feature.NetworkingPages import org.gdglille.devfest.android.theme.m3.partners.feature.PartnersListVM import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleListVM -import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakersListVM +import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakersListOrientableVM import org.gdglille.devfest.models.ui.ExportNetworkingUi import org.gdglille.devfest.repositories.AgendaRepository import org.gdglille.devfest.repositories.EventRepository @@ -158,7 +158,7 @@ fun Home( ) } composable(Screen.SpeakerList.route) { - SpeakersListVM( + SpeakersListOrientableVM( speakerRepository = speakerRepository, onSpeakerClicked = onSpeakerClicked ) diff --git a/theme-m3/speakers/speakers-feature/build.gradle.kts b/theme-m3/speakers/speakers-feature/build.gradle.kts index 905528335..5a99a690e 100644 --- a/theme-m3/speakers/speakers-feature/build.gradle.kts +++ b/theme-m3/speakers/speakers-feature/build.gradle.kts @@ -10,11 +10,8 @@ android { dependencies { implementation(projects.shared.core) - implementation(projects.themeM3.schedules.schedulesUi) - implementation(projects.themeM3.speakers.speakersUi) + implementation(projects.themeM3.speakers.speakersScreens) implementation(projects.themeM3.navigation) - implementation(projects.themeM3.style.schedules) - implementation(projects.themeM3.style.speakers) implementation(projects.themeM3.style.theme) implementation(libs.kotlinx.collections) diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailHorizontal.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailHorizontal.kt deleted file mode 100644 index 844e80679..000000000 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailHorizontal.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import org.gdglille.devfest.android.theme.m3.schedules.ui.talks.SmallScheduleItem -import org.gdglille.devfest.android.theme.m3.speakers.ui.SpeakerDetailSectionHorizontal -import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme -import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.android.theme.m3.style.appbars.TopAppBar -import org.gdglille.devfest.android.theme.m3.style.placeholder -import org.gdglille.devfest.android.theme.m3.style.previews.PHONE_LANDSCAPE -import org.gdglille.devfest.models.ui.SpeakerUi -import org.gdglille.devfest.models.ui.TalkItemUi - -@ExperimentalMaterial3Api -@Composable -fun SpeakerDetailHorizontal( - speaker: SpeakerUi, - onTalkClicked: (id: String) -> Unit, - onFavoriteClicked: (TalkItemUi) -> Unit, - onLinkClicked: (url: String) -> Unit, - onBackClicked: () -> Unit, - modifier: Modifier = Modifier, - isLoading: Boolean = false -) { - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - title = stringResource(id = R.string.screen_speaker_detail), - navigationIcon = { Back(onClick = onBackClicked) } - ) - }, - content = { - LazyVerticalGrid( - columns = GridCells.Fixed(count = 2), - modifier = Modifier.padding(it), - contentPadding = PaddingValues(vertical = 24.dp, horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - item(span = { GridItemSpan(2) }) { - SpeakerDetailSectionHorizontal( - speaker = speaker, - isLoading = isLoading, - onLinkClicked = onLinkClicked - ) - } - item(span = { GridItemSpan(2) }) { - Spacer(modifier = Modifier.height(24.dp)) - } - items(speaker.talks) { - SmallScheduleItem( - talk = it, - modifier = Modifier - .placeholder(visible = isLoading), - onFavoriteClicked = onFavoriteClicked, - onTalkClicked = onTalkClicked - ) - Spacer(modifier = Modifier.height(8.dp)) - } - } - } - ) -} - -@ExperimentalMaterial3Api -@Preview(device = PHONE_LANDSCAPE) -@Composable -private fun SpeakerDetailPreview() { - Conferences4HallTheme { - SpeakerDetailHorizontal( - speaker = SpeakerUi.fake, - onTalkClicked = {}, - onFavoriteClicked = {}, - onLinkClicked = {}, - onBackClicked = {} - ) - } -} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientable.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientable.kt deleted file mode 100644 index a6a1d39a4..000000000 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientable.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature - -import android.content.res.Configuration -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalConfiguration -import org.gdglille.devfest.models.ui.SpeakerUi -import org.gdglille.devfest.models.ui.TalkItemUi - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SpeakerDetailOrientable( - speaker: SpeakerUi, - onTalkClicked: (id: String) -> Unit, - onFavoriteClicked: (TalkItemUi) -> Unit, - onLinkClicked: (url: String) -> Unit, - onBackClicked: () -> Unit, - modifier: Modifier = Modifier, - isLoading: Boolean = false -) { - val orientation = LocalConfiguration.current - if (orientation.orientation == Configuration.ORIENTATION_LANDSCAPE) { - SpeakerDetailHorizontal( - speaker = speaker, - modifier = modifier, - onTalkClicked = onTalkClicked, - onFavoriteClicked = onFavoriteClicked, - onLinkClicked = onLinkClicked, - onBackClicked = onBackClicked, - isLoading = isLoading - ) - } else { - SpeakerDetailVertical( - speaker = speaker, - modifier = modifier, - onTalkClicked = onTalkClicked, - onFavoriteClicked = onFavoriteClicked, - onLinkClicked = onLinkClicked, - onBackClicked = onBackClicked, - isLoading = isLoading - ) - } -} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt new file mode 100644 index 000000000..6ee25e913 --- /dev/null +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt @@ -0,0 +1,65 @@ +package org.gdglille.devfest.android.theme.m3.speakers.feature + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.viewmodel.compose.viewModel +import org.gdglille.devfest.AlarmScheduler +import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakerDetailOrientable +import org.gdglille.devfest.android.theme.m3.style.R +import org.gdglille.devfest.android.theme.m3.style.appbars.TopAppBar +import org.gdglille.devfest.repositories.AgendaRepository + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SpeakerDetailOrientableVM( + speakerId: String, + agendaRepository: AgendaRepository, + alarmScheduler: AlarmScheduler, + onTalkClicked: (id: String) -> Unit, + onLinkClicked: (url: String) -> Unit, + onBackClicked: () -> Unit, + modifier: Modifier = Modifier, + viewModel: SpeakerDetailViewModel = viewModel( + factory = SpeakerDetailViewModel.Factory.create(speakerId, agendaRepository, alarmScheduler) + ) +) { + val context = LocalContext.current + val uiState = viewModel.uiState.collectAsState() + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = stringResource(id = R.string.screen_speaker_detail), + navigationIcon = { Back(onClick = onBackClicked) } + ) + }, + content = { + when (uiState.value) { + is SpeakerUiState.Loading -> SpeakerDetailOrientable( + speaker = (uiState.value as SpeakerUiState.Loading).speaker, + contentPadding = it, + onTalkClicked = {}, + onFavoriteClicked = {}, + onLinkClicked = {} + ) + + is SpeakerUiState.Failure -> Text(text = stringResource(id = R.string.text_error)) + is SpeakerUiState.Success -> SpeakerDetailOrientable( + speaker = (uiState.value as SpeakerUiState.Success).speaker, + contentPadding = it, + onTalkClicked = onTalkClicked, + onFavoriteClicked = { + viewModel.markAsFavorite(context, it) + }, + onLinkClicked = onLinkClicked, + ) + } + } + ) +} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVM.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVM.kt deleted file mode 100644 index 1a414082e..000000000 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVM.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel -import org.gdglille.devfest.AlarmScheduler -import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository - -@Composable -fun SpeakerDetailVM( - speakerId: String, - agendaRepository: AgendaRepository, - alarmScheduler: AlarmScheduler, - onTalkClicked: (id: String) -> Unit, - onLinkClicked: (url: String) -> Unit, - onBackClicked: () -> Unit, - modifier: Modifier = Modifier, - viewModel: SpeakerDetailViewModel = viewModel( - factory = SpeakerDetailViewModel.Factory.create(speakerId, agendaRepository, alarmScheduler) - ) -) { - val context = LocalContext.current - val uiState = viewModel.uiState.collectAsState() - when (uiState.value) { - is SpeakerUiState.Loading -> SpeakerDetailOrientable( - speaker = (uiState.value as SpeakerUiState.Loading).speaker, - modifier = modifier, - onTalkClicked = {}, - onFavoriteClicked = {}, - onLinkClicked = {}, - onBackClicked = onBackClicked - ) - - is SpeakerUiState.Failure -> Text(text = stringResource(id = R.string.text_error)) - is SpeakerUiState.Success -> SpeakerDetailOrientable( - speaker = (uiState.value as SpeakerUiState.Success).speaker, - modifier = modifier, - onTalkClicked = onTalkClicked, - onFavoriteClicked = { - viewModel.markAsFavorite(context, it) - }, - onLinkClicked = onLinkClicked, - onBackClicked = onBackClicked - ) - } -} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVertical.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVertical.kt deleted file mode 100644 index cd959032d..000000000 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailVertical.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import org.gdglille.devfest.android.theme.m3.schedules.ui.talks.MediumScheduleItem -import org.gdglille.devfest.android.theme.m3.speakers.ui.SpeakerDetailSectionVertical -import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme -import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.android.theme.m3.style.appbars.TopAppBar -import org.gdglille.devfest.android.theme.m3.style.placeholder -import org.gdglille.devfest.android.theme.m3.style.previews.ThemedPreviews -import org.gdglille.devfest.models.ui.SpeakerUi -import org.gdglille.devfest.models.ui.TalkItemUi - -@ExperimentalMaterial3Api -@Composable -fun SpeakerDetailVertical( - speaker: SpeakerUi, - onTalkClicked: (id: String) -> Unit, - onFavoriteClicked: (TalkItemUi) -> Unit, - onLinkClicked: (url: String) -> Unit, - onBackClicked: () -> Unit, - modifier: Modifier = Modifier, - isLoading: Boolean = false -) { - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - title = stringResource(id = R.string.screen_speaker_detail), - navigationIcon = { Back(onClick = onBackClicked) } - ) - }, - content = { - LazyColumn( - contentPadding = PaddingValues(vertical = 24.dp, horizontal = 16.dp), - modifier = Modifier.padding(it) - ) { - item { - SpeakerDetailSectionVertical( - speaker = speaker, - isLoading = isLoading, - onLinkClicked = onLinkClicked - ) - } - item { - Spacer(modifier = Modifier.height(24.dp)) - } - items(speaker.talks) { - MediumScheduleItem( - talk = it, - modifier = Modifier - .placeholder(visible = isLoading), - onFavoriteClicked = onFavoriteClicked, - onTalkClicked = onTalkClicked - ) - Spacer(modifier = Modifier.height(8.dp)) - } - } - } - ) -} - -@Suppress("UnusedPrivateMember") -@ExperimentalMaterial3Api -@ThemedPreviews -@Composable -private fun SpeakerDetailPreview() { - Conferences4HallTheme { - SpeakerDetailVertical( - speaker = SpeakerUi.fake, - onTalkClicked = {}, - onFavoriteClicked = {}, - onLinkClicked = {}, - onBackClicked = {} - ) - } -} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListVM.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListOrientableVM.kt similarity index 92% rename from theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListVM.kt rename to theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListOrientableVM.kt index 582c9f113..3b4ba96cc 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListVM.kt +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListOrientableVM.kt @@ -6,11 +6,12 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.lifecycle.viewmodel.compose.viewModel +import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakersListOrientable import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.repositories.SpeakerRepository @Composable -fun SpeakersListVM( +fun SpeakersListOrientableVM( speakerRepository: SpeakerRepository, onSpeakerClicked: (id: String) -> Unit, modifier: Modifier = Modifier, diff --git a/theme-m3/speakers/speakers-screens/.gitignore b/theme-m3/speakers/speakers-screens/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/speakers/speakers-screens/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/speakers/speakers-screens/build.gradle.kts b/theme-m3/speakers/speakers-screens/build.gradle.kts new file mode 100644 index 000000000..7de352d9d --- /dev/null +++ b/theme-m3/speakers/speakers-screens/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.android.library.compose") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.speakers.screens" +} + +dependencies { + implementation(projects.shared.core) + implementation(projects.themeM3.schedules.schedulesUi) + implementation(projects.themeM3.speakers.speakersUi) + implementation(projects.themeM3.style.schedules) + implementation(projects.themeM3.style.speakers) + implementation(projects.themeM3.style.theme) + + implementation(libs.kotlinx.collections) + + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.tooling) +} diff --git a/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerAvatarScreen.kt b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerAvatarScreen.kt new file mode 100644 index 000000000..1973f6e82 --- /dev/null +++ b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerAvatarScreen.kt @@ -0,0 +1,39 @@ +package org.gdglille.devfest.android.theme.m3.speakers.screens + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme +import org.gdglille.devfest.android.theme.m3.style.placeholder +import org.gdglille.devfest.android.theme.m3.style.speakers.avatar.MediumSpeakerAvatar + +@Composable +fun SpeakerAvatarScreen( + url: String, + modifier: Modifier = Modifier, + isLoading: Boolean = false +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + MediumSpeakerAvatar( + url = url, + contentDescription = null, + modifier = Modifier.placeholder(visible = isLoading) + ) + } +} + +@Preview +@Composable +private fun SpeakerAvatarScreenPreview() { + Conferences4HallTheme { + SpeakerAvatarScreen( + url = "" + ) + } +} diff --git a/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailOrientable.kt b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailOrientable.kt new file mode 100644 index 000000000..51284be85 --- /dev/null +++ b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailOrientable.kt @@ -0,0 +1,61 @@ +package org.gdglille.devfest.android.theme.m3.speakers.screens + +import android.content.res.Configuration +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import org.gdglille.devfest.models.ui.SpeakerUi +import org.gdglille.devfest.models.ui.TalkItemUi + +@Composable +fun SpeakerDetailOrientable( + speaker: SpeakerUi, + onTalkClicked: (id: String) -> Unit, + onFavoriteClicked: (TalkItemUi) -> Unit, + onLinkClicked: (url: String) -> Unit, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), + isLoading: Boolean = false +) { + val state = rememberLazyListState() + val orientation = LocalConfiguration.current + if (orientation.orientation == Configuration.ORIENTATION_LANDSCAPE) { + Row( + verticalAlignment = Alignment.Top, + modifier = modifier.padding(contentPadding) + ) { + SpeakerAvatarScreen( + url = speaker.url, + isLoading = isLoading, + modifier = Modifier.weight(1f) + ) + SpeakerDetailVerticalScreen( + speaker = speaker, + onTalkClicked = onTalkClicked, + onFavoriteClicked = onFavoriteClicked, + onLinkClicked = onLinkClicked, + modifier = Modifier.weight(1f), + state = state, + isLoading = isLoading, + displayAvatar = false + ) + } + } else { + SpeakerDetailVerticalScreen( + speaker = speaker, + onTalkClicked = onTalkClicked, + onFavoriteClicked = onFavoriteClicked, + onLinkClicked = onLinkClicked, + modifier = modifier, + state = state, + contentPadding = contentPadding, + isLoading = isLoading + ) + } +} diff --git a/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailVerticalScreen.kt b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailVerticalScreen.kt new file mode 100644 index 000000000..b874c2454 --- /dev/null +++ b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerDetailVerticalScreen.kt @@ -0,0 +1,80 @@ +package org.gdglille.devfest.android.theme.m3.speakers.screens + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.gdglille.devfest.android.theme.m3.schedules.ui.talks.MediumScheduleItem +import org.gdglille.devfest.android.theme.m3.speakers.ui.SpeakerDetailSectionVertical +import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme +import org.gdglille.devfest.android.theme.m3.style.placeholder +import org.gdglille.devfest.android.theme.m3.style.previews.ThemedPreviews +import org.gdglille.devfest.models.ui.SpeakerUi +import org.gdglille.devfest.models.ui.TalkItemUi + +@Composable +fun SpeakerDetailVerticalScreen( + speaker: SpeakerUi, + onTalkClicked: (id: String) -> Unit, + onFavoriteClicked: (TalkItemUi) -> Unit, + onLinkClicked: (url: String) -> Unit, + modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + contentPadding: PaddingValues = PaddingValues(0.dp), + isLoading: Boolean = false, + displayAvatar: Boolean = true, +) { + LazyColumn( + contentPadding = PaddingValues(vertical = 24.dp, horizontal = 16.dp), + state = state, + modifier = modifier.padding(contentPadding) + ) { + item { + SpeakerDetailSectionVertical( + speaker = speaker, + isLoading = isLoading, + displayAvatar = displayAvatar, + onLinkClicked = onLinkClicked + ) + } + item { + Spacer(modifier = Modifier.height(24.dp)) + } + items(speaker.talks) { + MediumScheduleItem( + talk = it, + modifier = Modifier + .placeholder(visible = isLoading), + onFavoriteClicked = onFavoriteClicked, + onTalkClicked = onTalkClicked + ) + Spacer(modifier = Modifier.height(8.dp)) + } + } +} + +@Suppress("UnusedPrivateMember") +@ThemedPreviews +@Composable +private fun SpeakerDetailVerticalScreenPreview() { + Conferences4HallTheme { + Scaffold { + SpeakerDetailVerticalScreen( + speaker = SpeakerUi.fake, + onTalkClicked = {}, + onFavoriteClicked = {}, + onLinkClicked = {}, + contentPadding = it + ) + } + } +} diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerListOrientable.kt b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerListOrientable.kt similarity index 85% rename from theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerListOrientable.kt rename to theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerListOrientable.kt index 63865e299..7a678e869 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerListOrientable.kt +++ b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakerListOrientable.kt @@ -1,4 +1,4 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature +package org.gdglille.devfest.android.theme.m3.speakers.screens import android.content.res.Configuration import androidx.compose.foundation.lazy.grid.rememberLazyGridState @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import kotlinx.collections.immutable.ImmutableList +import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakersListScreen import org.gdglille.devfest.models.ui.SpeakerItemUi @Composable @@ -15,10 +16,10 @@ fun SpeakersListOrientable( modifier: Modifier = Modifier, isLoading: Boolean = false, ) { - val orientation = LocalConfiguration.current val state = rememberLazyGridState() + val orientation = LocalConfiguration.current if (orientation.orientation == Configuration.ORIENTATION_LANDSCAPE) { - SpeakersList( + SpeakersListScreen( speakers = speakers, columnCount = 4, modifier = modifier, @@ -27,7 +28,7 @@ fun SpeakersListOrientable( isLoading = isLoading ) } else { - SpeakersList( + SpeakersListScreen( speakers = speakers, columnCount = 2, modifier = modifier, diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersList.kt b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakersListScreen.kt similarity index 95% rename from theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersList.kt rename to theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakersListScreen.kt index 756bf4918..9d5c68734 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersList.kt +++ b/theme-m3/speakers/speakers-screens/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/screens/SpeakersListScreen.kt @@ -1,4 +1,4 @@ -package org.gdglille.devfest.android.theme.m3.speakers.feature +package org.gdglille.devfest.android.theme.m3.speakers.screens import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -21,7 +21,7 @@ import org.gdglille.devfest.android.theme.m3.style.toDp import org.gdglille.devfest.models.ui.SpeakerItemUi @Composable -fun SpeakersList( +fun SpeakersListScreen( speakers: ImmutableList, onSpeakerClicked: (id: String) -> Unit, modifier: Modifier = Modifier, @@ -54,7 +54,7 @@ fun SpeakersList( @Composable private fun SpeakersListPreview() { Conferences4HallTheme { - SpeakersList( + SpeakersListScreen( speakers = persistentListOf( SpeakerItemUi.fake.copy(id = "1"), SpeakerItemUi.fake.copy(id = "2"), diff --git a/theme-m3/speakers/speakers-ui/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/ui/SpeakerDetailSection.kt b/theme-m3/speakers/speakers-ui/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/ui/SpeakerDetailSection.kt index abe33394d..5433c0b7f 100644 --- a/theme-m3/speakers/speakers-ui/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/ui/SpeakerDetailSection.kt +++ b/theme-m3/speakers/speakers-ui/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/ui/SpeakerDetailSection.kt @@ -1,13 +1,10 @@ package org.gdglille.devfest.android.theme.m3.speakers.ui import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -26,18 +23,21 @@ fun SpeakerDetailSectionVertical( onLinkClicked: (url: String) -> Unit, modifier: Modifier = Modifier, isLoading: Boolean = false, + displayAvatar: Boolean = true, ) { Column( modifier = modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface) ) { - MediumSpeakerAvatar( - url = speaker.url, - contentDescription = null, - modifier = Modifier.placeholder(visible = isLoading) - ) - Spacer(modifier = Modifier.height(16.dp)) + if (displayAvatar) { + MediumSpeakerAvatar( + url = speaker.url, + contentDescription = null, + modifier = Modifier.placeholder(visible = isLoading) + ) + Spacer(modifier = Modifier.height(16.dp)) + } SocialsSection( title = speaker.name, pronouns = speaker.pronouns, @@ -58,45 +58,6 @@ fun SpeakerDetailSectionVertical( } } -@Composable -fun SpeakerDetailSectionHorizontal( - speaker: SpeakerUi, - onLinkClicked: (url: String) -> Unit, - modifier: Modifier = Modifier, - isLoading: Boolean = false, -) { - Column( - modifier = modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { - MediumSpeakerAvatar( - url = speaker.url, - contentDescription = null, - modifier = Modifier.placeholder(visible = isLoading) - ) - SocialsSection( - title = speaker.name, - pronouns = speaker.pronouns, - subtitle = speaker.activity, - isLoading = isLoading, - twitterUrl = speaker.twitterUrl, - mastodonUrl = speaker.mastodonUrl, - githubUrl = speaker.githubUrl, - linkedinUrl = speaker.linkedinUrl, - websiteUrl = speaker.websiteUrl, - onLinkClicked = onLinkClicked - ) - } - MarkdownText( - text = speaker.bio, - modifier = Modifier.placeholder(visible = isLoading) - ) - } -} - @Suppress("UnusedPrivateMember") @ThemedPreviews @Composable @@ -108,15 +69,3 @@ private fun SpeakerDetailSectionVerticalPreview() { ) } } - -@Suppress("UnusedPrivateMember") -@ThemedPreviews -@Composable -private fun SpeakerDetailSectionHorizontalPreview() { - Conferences4HallTheme { - SpeakerDetailSectionHorizontal( - speaker = SpeakerUi.fake, - onLinkClicked = {} - ) - } -}