diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreen.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreen.kt index 819d885d8..7fac03229 100644 --- a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreen.kt +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreen.kt @@ -176,9 +176,7 @@ private fun TimetableItemDetailScreen( if (uiState is Loaded) { ProvideRoomTheme(uiState.roomThemeKey) { TimetableItemDetailTopAppBar( - isLangSelectable = uiState.isLangSelectable, onNavigationIconClick = onNavigationIconClick, - onSelectedLanguage = onSelectedLanguage, scrollBehavior = scrollBehavior, ) } @@ -229,6 +227,8 @@ private fun TimetableItemDetailScreen( TimetableItemDetailHeadline( currentLang = uiState.currentLang, timetableItem = uiState.timetableItem, + isLangSelectable = uiState.isLangSelectable, + onLanguageSelect = onSelectedLanguage, ) } diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailHeadline.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailHeadline.kt index ca2e15765..4d777a48a 100644 --- a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailHeadline.kt +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailHeadline.kt @@ -1,10 +1,12 @@ package io.github.droidkaigi.confsched.sessions.component +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -12,16 +14,34 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.CollectionInfo +import androidx.compose.ui.semantics.CollectionItemInfo +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.collectionInfo +import androidx.compose.ui.semantics.collectionItemInfo +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.selected +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import conference_app_2024.feature.sessions.generated.resources.english +import conference_app_2024.feature.sessions.generated.resources.japanese +import conference_app_2024.feature.sessions.generated.resources.select_language import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.designsystem.theme.LocalRoomTheme import io.github.droidkaigi.confsched.droidkaigiui.component.TimetableItemTag @@ -29,6 +49,8 @@ import io.github.droidkaigi.confsched.droidkaigiui.rememberAsyncImagePainter import io.github.droidkaigi.confsched.model.Lang import io.github.droidkaigi.confsched.model.TimetableItem import io.github.droidkaigi.confsched.model.fake +import io.github.droidkaigi.confsched.sessions.SessionsRes +import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview const val TimetableItemDetailHeadlineTestTag = "TimetableItemDetailHeadlineTestTag" @@ -37,17 +59,19 @@ const val TimetableItemDetailHeadlineTestTag = "TimetableItemDetailHeadlineTestT fun TimetableItemDetailHeadline( currentLang: Lang?, timetableItem: TimetableItem, + isLangSelectable: Boolean, + onLanguageSelect: (Lang) -> Unit, modifier: Modifier = Modifier, ) { - val currentLang = currentLang ?: Lang.ENGLISH + val currentLang = currentLang ?: timetableItem.language.toLang() Column( modifier = modifier // FIXME: Implement and use a theme color instead of fixed colors like RoomColors.primary and RoomColors.primaryDim .background(LocalRoomTheme.current.dimColor) - .padding(8.dp) + .padding(horizontal = 8.dp) .fillMaxWidth(), ) { - Row { + Row(verticalAlignment = Alignment.CenterVertically) { TimetableItemTag( tagText = timetableItem.room.name.currentLangTitle, tagColor = LocalRoomTheme.current.primaryColor, @@ -59,6 +83,13 @@ fun TimetableItemDetailHeadline( tagColor = MaterialTheme.colorScheme.onSurfaceVariant, ) } + Spacer(modifier = Modifier.weight(1f)) + if (isLangSelectable) { + LanguageSwitcher( + currentLang = currentLang, + onLanguageSelect = onLanguageSelect, + ) + } } Spacer(modifier = Modifier.height(16.dp)) Text( @@ -98,6 +129,83 @@ fun TimetableItemDetailHeadline( } } +@Composable +private fun LanguageSwitcher( + currentLang: Lang, + onLanguageSelect: (Lang) -> Unit, + modifier: Modifier = Modifier, +) { + val normalizedCurrentLang = if (currentLang == Lang.MIXED) { + Lang.ENGLISH + } else { + currentLang + } + val availableLangs: Map = mapOf( + stringResource(SessionsRes.string.japanese) to Lang.JAPANESE, + stringResource(SessionsRes.string.english) to Lang.ENGLISH, + ) + val switcherContentDescription = stringResource(SessionsRes.string.select_language) + Row( + modifier = modifier + .selectableGroup() + .semantics { + contentDescription = switcherContentDescription + collectionInfo = CollectionInfo( + rowCount = availableLangs.size, + columnCount = 1, + ) + }, + verticalAlignment = Alignment.CenterVertically, + ) { + val lastIndex = availableLangs.size - 1 + availableLangs.entries.forEachIndexed { index, (label, lang) -> + val isSelected = normalizedCurrentLang == lang + TextButton( + onClick = { onLanguageSelect(lang) }, + modifier = Modifier + .semantics { + role = Role.Tab + selected = isSelected + collectionItemInfo = CollectionItemInfo( + rowIndex = index, + rowSpan = 1, + columnIndex = 0, + columnSpan = 1, + ) + }, + contentPadding = PaddingValues(12.dp), + ) { + val contentColor = if (isSelected) { + LocalRoomTheme.current.primaryColor + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } + AnimatedVisibility(isSelected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + modifier = Modifier + .padding(end = 4.dp) + .size(12.dp), + tint = contentColor, + ) + } + Text( + text = label, + color = contentColor, + style = MaterialTheme.typography.labelMedium, + ) + } + if (index < lastIndex) { + VerticalDivider( + modifier = Modifier.height(11.dp), + color = MaterialTheme.colorScheme.outlineVariant, + ) + } + } + } +} + @Composable @Preview fun TimetableItemDetailHeadlinePreview() { @@ -107,6 +215,8 @@ fun TimetableItemDetailHeadlinePreview() { TimetableItemDetailHeadline( timetableItem = TimetableItem.Session.fake(), currentLang = Lang.JAPANESE, + isLangSelectable = true, + onLanguageSelect = {}, ) } } @@ -122,6 +232,8 @@ fun TimetableItemDetailHeadlineWithEnglishPreview() { TimetableItemDetailHeadline( timetableItem = TimetableItem.Session.fake(), currentLang = Lang.ENGLISH, + isLangSelectable = true, + onLanguageSelect = {}, ) } } @@ -137,6 +249,8 @@ fun TimetableItemDetailHeadlineWithMixedPreview() { TimetableItemDetailHeadline( timetableItem = TimetableItem.Session.fake(), currentLang = Lang.MIXED, + isLangSelectable = true, + onLanguageSelect = {}, ) } } diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailTopAppBar.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailTopAppBar.kt index 130995abc..8ae418530 100644 --- a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailTopAppBar.kt +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailTopAppBar.kt @@ -8,15 +8,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack -import androidx.compose.material.icons.outlined.GTranslate -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior @@ -24,23 +19,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import conference_app_2024.feature.sessions.generated.resources.english -import conference_app_2024.feature.sessions.generated.resources.japanese import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.designsystem.theme.LocalRoomTheme -import io.github.droidkaigi.confsched.model.Lang -import io.github.droidkaigi.confsched.sessions.SessionsRes -import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @OptIn(ExperimentalMaterial3Api::class) @Composable fun TimetableItemDetailTopAppBar( - isLangSelectable: Boolean, onNavigationIconClick: () -> Unit, - onSelectedLanguage: (Lang) -> Unit, scrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier, ) { @@ -68,48 +55,6 @@ fun TimetableItemDetailTopAppBar( ) } }, - actions = { - if (isLangSelectable) { - var expanded by remember { mutableStateOf(false) } - - IconButton(onClick = { expanded = true }) { - Icon( - imageVector = Icons.Outlined.GTranslate, - contentDescription = "Select Language", - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - DropdownMenuItem( - text = { - Text( - text = stringResource(SessionsRes.string.japanese), - style = MaterialTheme.typography.bodySmall, - ) - }, - onClick = { - onSelectedLanguage(Lang.JAPANESE) - expanded = false - }, - ) - DropdownMenuItem( - text = { - Text( - text = stringResource(SessionsRes.string.english), - style = MaterialTheme.typography.bodySmall, - ) - }, - onClick = { - onSelectedLanguage(Lang.ENGLISH) - expanded = false - }, - ) - } - } - }, scrollBehavior = scrollBehavior, ) } @@ -122,9 +67,7 @@ fun TimetableItemDetailTopAppBarPreview() { ProvideFakeRoomTheme { Surface { TimetableItemDetailTopAppBar( - isLangSelectable = true, onNavigationIconClick = {}, - onSelectedLanguage = {}, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), ) } @@ -140,9 +83,7 @@ fun TimetableItemDetailTopAppBarUnSelectablePreview() { ProvideFakeRoomTheme { Surface { TimetableItemDetailTopAppBar( - isLangSelectable = false, onNavigationIconClick = {}, - onSelectedLanguage = {}, scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), ) }