Skip to content

Commit

Permalink
feat(speakers): support adaptive layout.
Browse files Browse the repository at this point in the history
  • Loading branch information
GerardPaligot committed Jan 21, 2024
1 parent 4b79c16 commit abf6da3
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.gdglille.devfest.android.theme

import android.content.res.Configuration
import androidx.compose.material3.Icon
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
Expand Down Expand Up @@ -38,8 +39,8 @@ import org.gdglille.devfest.android.theme.m3.partners.feature.PartnersListCompac
import org.gdglille.devfest.android.theme.m3.schedules.feature.AgendaFiltersCompactVM
import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleDetailOrientableVM
import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleGridAdaptive
import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakerAdaptive
import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakerDetailOrientableVM
import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakersListCompactVM
import org.gdglille.devfest.android.theme.m3.style.appbars.iconColor
import org.gdglille.devfest.models.ui.ExportNetworkingUi
import org.gdglille.devfest.models.ui.VCardModel
Expand Down Expand Up @@ -184,8 +185,10 @@ fun MainNavigation(
)
}
composable(Screen.SpeakerList.route) {
SpeakersListCompactVM(
onSpeakerClicked = { navController.navigate(Screen.Speaker.route(it)) }
SpeakerAdaptive(
showBackInDetail = adaptiveInfo.windowSizeClass.widthSizeClass.isCompat,
onTalkClicked = { navController.navigate(Screen.Schedule.route(it)) },
onLinkClicked = { launchUrl(it) }
)
}
composable(
Expand All @@ -196,7 +199,8 @@ fun MainNavigation(
speakerId = it.arguments?.getString("speakerId")!!,
onTalkClicked = { navController.navigate(Screen.Schedule.route(it)) },
onLinkClicked = { launchUrl(it) },
onBackClicked = { navController.popBackStack() }
navigationIcon = { Back { navController.popBackStack() } },
isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
)
}
composable(Screen.MyProfile.route) {
Expand Down
1 change: 1 addition & 0 deletions theme-m3/speakers/speakers-feature/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {

implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.tooling)
implementation(libs.androidx.compose.lifecycle)
implementation(libs.androidx.lifecycle.vm)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.gdglille.devfest.android.theme.m3.speakers.feature

import androidx.compose.material3.adaptive.AnimatedPane
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun SpeakerAdaptive(
showBackInDetail: Boolean,
onTalkClicked: (id: String) -> Unit,
onLinkClicked: (url: String) -> Unit,
modifier: Modifier = Modifier
) {
val navigator = rememberListDetailPaneScaffoldNavigator()
var selectedItem: String? by rememberSaveable { mutableStateOf(null) }
ListDetailPaneScaffold(
scaffoldState = navigator.scaffoldState,
modifier = modifier,
listPane = {
AnimatedPane(Modifier) {
SpeakersListCompactVM(
onSpeakerClicked = {
selectedItem = it
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
}
)
}
},
detailPane = {
AnimatedPane(modifier = Modifier) {
selectedItem?.let { item ->
SpeakerDetailOrientableVM(
speakerId = item,
onTalkClicked = onTalkClicked,
onLinkClicked = onLinkClicked,
navigationIcon = if (showBackInDetail) {
@Composable { Back {
if (navigator.canNavigateBack()) {
navigator.navigateBack()
}
} }
} else null
)
}
}
}
)
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,58 @@
package org.gdglille.devfest.android.theme.m3.speakers.feature

import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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.android.theme.m3.style.Scaffold
import org.gdglille.devfest.android.theme.m3.style.appbars.AppBarIcons
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SpeakerDetailOrientableVM(
speakerId: String,
onTalkClicked: (id: String) -> Unit,
onLinkClicked: (url: String) -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SpeakerDetailViewModel = koinViewModel(parameters = { parametersOf(speakerId) })
navigationIcon: @Composable (AppBarIcons.() -> Unit)? = null,
isLandscape: Boolean = false,
viewModel: SpeakerDetailViewModel = koinViewModel(key = speakerId, parameters = { parametersOf(speakerId) })
) {
val context = LocalContext.current
val uiState = viewModel.uiState.collectAsState()
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = stringResource(id = R.string.screen_speaker_detail),
navigationIcon = { Back(onClick = onBackClicked) },
scrollBehavior = scrollBehavior
title = stringResource(id = R.string.screen_speaker_detail),
navigationIcon = navigationIcon,
modifier = modifier
) {
when (uiState.value) {
is SpeakerUiState.Loading -> SpeakerDetailOrientable(
speaker = (uiState.value as SpeakerUiState.Loading).speaker,
contentPadding = it,
onTalkClicked = {},
onFavoriteClicked = {},
onLinkClicked = {},
isLandscape = isLandscape
)
},
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,
)
}
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,
isLandscape = isLandscape
)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,59 +1,49 @@
package org.gdglille.devfest.android.theme.m3.speakers.feature

import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
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.LocalConfiguration
import androidx.compose.ui.res.stringResource
import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakersListScreen
import org.gdglille.devfest.android.theme.m3.style.R
import org.gdglille.devfest.android.theme.m3.style.Scaffold
import org.koin.androidx.compose.koinViewModel

private const val ColumnCountLandscape = 4
private const val ColumnCountPortrait = 2

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SpeakersListCompactVM(
onSpeakerClicked: (id: String) -> Unit,
modifier: Modifier = Modifier,
viewModel: SpeakersListViewModel = koinViewModel()
) {
val configuration = LocalConfiguration.current
val state = rememberLazyGridState()
val uiState = viewModel.uiState.collectAsState()
val columnCount =
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) ColumnCountLandscape
else ColumnCountPortrait
Scaffold(
title = stringResource(id = R.string.screen_speakers),
modifier = modifier
modifier = modifier,
hasScrollBehavior = false
) {
when (uiState.value) {
is SpeakersUiState.Loading -> SpeakersListScreen(
speakers = (uiState.value as SpeakersUiState.Loading).speakers,
columnCount = columnCount,
state = state,
onSpeakerClicked = onSpeakerClicked,
isLoading = true,
modifier = Modifier.padding(it)
modifier = Modifier.padding(it),
state = state,
isLoading = true
)

is SpeakersUiState.Failure -> Text(text = stringResource(id = R.string.text_error))
is SpeakersUiState.Success -> {
SpeakersListScreen(
speakers = (uiState.value as SpeakersUiState.Success).speakers,
columnCount = columnCount,
state = state,
onSpeakerClicked = onSpeakerClicked,
isLoading = false,
modifier = Modifier.padding(it)
modifier = Modifier.padding(top = it.calculateTopPadding()),
state = state,
isLoading = false
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
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
Expand All @@ -21,11 +19,11 @@ fun SpeakerDetailOrientable(
onLinkClicked: (url: String) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
isLandscape: Boolean = false,
isLoading: Boolean = false
) {
val state = rememberLazyListState()
val orientation = LocalConfiguration.current
if (orientation.orientation == Configuration.ORIENTATION_LANDSCAPE) {
if (isLandscape) {
Row(
verticalAlignment = Alignment.Top,
modifier = modifier.padding(contentPadding)
Expand All @@ -35,7 +33,7 @@ fun SpeakerDetailOrientable(
isLoading = isLoading,
modifier = Modifier.weight(1f)
)
SpeakerDetailVerticalScreen(
SpeakerDetailScreen(
speaker = speaker,
onTalkClicked = onTalkClicked,
onFavoriteClicked = onFavoriteClicked,
Expand All @@ -47,7 +45,7 @@ fun SpeakerDetailOrientable(
)
}
} else {
SpeakerDetailVerticalScreen(
SpeakerDetailScreen(
speaker = speaker,
onTalkClicked = onTalkClicked,
onFavoriteClicked = onFavoriteClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ 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
Expand All @@ -22,7 +21,7 @@ import org.gdglille.devfest.models.ui.SpeakerUi
import org.gdglille.devfest.models.ui.TalkItemUi

@Composable
fun SpeakerDetailVerticalScreen(
fun SpeakerDetailScreen(
speaker: SpeakerUi,
onTalkClicked: (id: String) -> Unit,
onFavoriteClicked: (TalkItemUi) -> Unit,
Expand Down Expand Up @@ -65,10 +64,10 @@ fun SpeakerDetailVerticalScreen(
@Suppress("UnusedPrivateMember")
@ThemedPreviews
@Composable
private fun SpeakerDetailVerticalScreenPreview() {
private fun SpeakerDetailScreenPreview() {
Conferences4HallTheme {
Scaffold {
SpeakerDetailVerticalScreen(
SpeakerDetailScreen(
speaker = SpeakerUi.fake,
onTalkClicked = {},
onFavoriteClicked = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme
Expand All @@ -26,16 +27,15 @@ fun SpeakersListScreen(
onSpeakerClicked: (id: String) -> Unit,
modifier: Modifier = Modifier,
state: LazyGridState = rememberLazyGridState(),
columnCount: Int = 2,
isLoading: Boolean = false,
) {
LazyVerticalGrid(
columns = GridCells.Fixed(count = columnCount),
columns = GridCells.Adaptive(minSize = 150.dp),
modifier = modifier.fillMaxWidth(),
state = state,
verticalArrangement = Arrangement.spacedBy(SpacingTokens.MediumSpacing.toDp()),
horizontalArrangement = Arrangement.spacedBy(SpacingTokens.MediumSpacing.toDp()),
contentPadding = PaddingValues(SpacingTokens.LargeSpacing.toDp()),
contentPadding = PaddingValues(vertical = SpacingTokens.ExtraLargeSpacing.toDp()),
content = {
items(speakers.toList(), key = { it.id }) {
LargeSpeakerItem(
Expand Down

0 comments on commit abf6da3

Please sign in to comment.