diff --git a/feature/sessions/src/commonMain/res/drawable/calendar_add_on.xml b/feature/sessions/src/commonMain/composeResources/drawable/calendar_add_on.xml similarity index 100% rename from feature/sessions/src/commonMain/res/drawable/calendar_add_on.xml rename to feature/sessions/src/commonMain/composeResources/drawable/calendar_add_on.xml diff --git a/feature/sessions/src/commonMain/res/drawable/icon_droidkaigi_logo.xml b/feature/sessions/src/commonMain/composeResources/drawable/icon_droidkaigi_logo.xml similarity index 100% rename from feature/sessions/src/commonMain/res/drawable/icon_droidkaigi_logo.xml rename to feature/sessions/src/commonMain/composeResources/drawable/icon_droidkaigi_logo.xml 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 d69b359ac..eb25d8100 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 @@ -4,19 +4,15 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -25,7 +21,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.testTag import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable @@ -36,13 +31,16 @@ import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.model.Lang -import io.github.droidkaigi.confsched.model.Lang.JAPANESE -import io.github.droidkaigi.confsched.model.MultiLangText import io.github.droidkaigi.confsched.model.TimetableItem import io.github.droidkaigi.confsched.model.TimetableItem.Session import io.github.droidkaigi.confsched.model.fake import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenUiState.Loaded import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenUiState.Loading +import io.github.droidkaigi.confsched.sessions.component.TimeTableItemDetailContent +import io.github.droidkaigi.confsched.sessions.component.TimeTableItemDetailHeadline +import io.github.droidkaigi.confsched.sessions.component.TimeTableItemDetailSummaryCard +import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailBottomAppBar +import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailTopAppBar import io.github.droidkaigi.confsched.ui.SnackbarMessageEffect import io.github.droidkaigi.confsched.ui.UserMessageStateHolder import io.github.droidkaigi.confsched.ui.UserMessageStateHolderImpl @@ -151,82 +149,49 @@ private fun TimetableItemDetailScreen( .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { if (uiState is Loaded) { - LargeTopAppBar(title = { - Row { - Button(onClick = { onNavigationIconClick() }) { - Text( - text = "Back", - style = MaterialTheme.typography.bodySmall, - ) - } - Text( - modifier = Modifier.weight(1F), - text = uiState.timetableItem.title.currentLangTitle, - style = MaterialTheme.typography.bodyLarge, - ) - if (uiState.isLangSelectable) { - Button(onClick = { onSelectedLanguage(JAPANESE) }) { - Text( - text = "日本語", - style = MaterialTheme.typography.bodySmall, - ) - } - Button(onClick = { onSelectedLanguage(Lang.ENGLISH) }) { - Text( - text = "English", - style = MaterialTheme.typography.bodySmall, - ) - } - } - } - }) + TimetableItemDetailTopAppBar( + isLangSelectable = uiState.isLangSelectable, + onNavigationIconClick = onNavigationIconClick, + onSelectedLanguage = onSelectedLanguage, + scrollBehavior = scrollBehavior, + ) } }, bottomBar = { if (uiState is Loaded) { - Column { - Button( - modifier = Modifier.testTag(TimetableItemDetailBookmarkIconTestTag), - onClick = { onBookmarkClick(uiState.timetableItem) }, - ) { - Text(text = "Bookmark: ${uiState.isBookmarked}") - } - Button( - - onClick = { onCalendarRegistrationClick(uiState.timetableItem) }, - ) { - Text(text = "Calendar") - } - Button(onClick = { onShareClick(uiState.timetableItem) }) { - Text(text = "Share") - } - } + TimetableItemDetailBottomAppBar( + timetableItem = uiState.timetableItem, + isBookmarked = uiState.isBookmarked, + onBookmarkClick = onBookmarkClick, + onCalendarRegistrationClick = onCalendarRegistrationClick, + onShareClick = onShareClick, + ) } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { innerPadding -> if (uiState is Loaded) { - Column( - Modifier.padding( - top = innerPadding.calculateTopPadding(), - ), + LazyColumn( + modifier = Modifier.fillMaxSize().padding(innerPadding), ) { - val currentLang = uiState.currentLang ?: Lang.ENGLISH - fun MultiLangText.getByLang(lang: Lang): String { - return if (lang == JAPANESE) { - jaTitle - } else { - enTitle - } + item { + TimeTableItemDetailHeadline( + timetableItem = uiState.timetableItem, + ) } - Text( - text = when (val item = uiState.timetableItem) { - is TimetableItem.Session -> item.description.getByLang(currentLang) - is TimetableItem.Special -> item.description.getByLang(currentLang) - }, - ) - Button(onClick = { onLinkClick(uiState.timetableItem.url) }) { - Text(text = "Link") + + item { + TimeTableItemDetailSummaryCard( + timetableItem = uiState.timetableItem, + ) + } + + item { + TimeTableItemDetailContent( + timetableItem = uiState.timetableItem, + currentLang = uiState.currentLang, + onLinkClick = onLinkClick, + ) } } } diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailContent.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailContent.kt new file mode 100644 index 000000000..dcbaea407 --- /dev/null +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailContent.kt @@ -0,0 +1,237 @@ +package io.github.droidkaigi.confsched.sessions.component + +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.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Description +import androidx.compose.material.icons.outlined.PlayCircle +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import io.github.droidkaigi.confsched.designsystem.component.ClickableLinkText +import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched.model.Lang +import io.github.droidkaigi.confsched.model.MultiLangText +import io.github.droidkaigi.confsched.model.TimetableItem +import io.github.droidkaigi.confsched.model.TimetableItem.Session +import io.github.droidkaigi.confsched.model.TimetableItem.Special +import io.github.droidkaigi.confsched.model.fake + +@Composable +fun TimeTableItemDetailContent( + timetableItem: TimetableItem, + currentLang: Lang?, + modifier: Modifier = Modifier, + onLinkClick: (url: String) -> Unit, +) { + Column(modifier = modifier) { + when (timetableItem) { + is Session -> { + val currentLang = currentLang ?: Lang.ENGLISH + fun MultiLangText.getByLang(lang: Lang): String { + return if (lang == Lang.JAPANESE) { + jaTitle + } else { + enTitle + } + } + DescriptionSection( + description = timetableItem.description.getByLang(currentLang), + onLinkClick = onLinkClick, + ) + TargetAudienceSection(timetableItem = timetableItem) + if (timetableItem.asset.isAvailable) { + ArchiveSection( + timetableItem = timetableItem, + onViewSlideClick = onLinkClick, + onWatchVideoClick = onLinkClick, + ) + } + } + + is Special -> { + Text("Special") + } + } + } +} + +@Composable +private fun DescriptionSection( + description: String, + onLinkClick: (url: String) -> Unit, +) { + // TODO: switch color according to room type + var isExpand by remember { mutableStateOf(false) } + + Column(modifier = Modifier.padding(8.dp)) { + ClickableLinkText( + content = description, + regex = "(https)(://[\\w/:%#$&?()~.=+\\-]+)".toRegex(), + onLinkClick = onLinkClick, + style = MaterialTheme.typography.bodyLarge, + maxLines = if (isExpand) Int.MAX_VALUE else 7, + overflow = if (isExpand) TextOverflow.Clip else TextOverflow.Ellipsis, + ) + Spacer(Modifier.height(16.dp)) + if (isExpand.not()) { + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { isExpand = true }, + ) { + Text( + text = "続きを読む", + style = MaterialTheme.typography.labelLarge, + color = Color(0xFF45E761), + ) + } + } + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +private fun TargetAudienceSection( + timetableItem: TimetableItem, + modifier: Modifier = Modifier, +) { + // TODO: switch color according to room type + Column(modifier = modifier.padding(8.dp)) { + Text( + text = "対象者", + style = MaterialTheme.typography.titleLarge, + color = Color(0xFF45E761), + ) + Spacer(Modifier.height(8.dp)) + Text( + text = timetableItem.targetAudience, + style = MaterialTheme.typography.bodyLarge, + ) + Spacer(Modifier.height(8.dp)) + } +} + +@Composable +private fun ArchiveSection( + timetableItem: TimetableItem, + onViewSlideClick: (url: String) -> Unit, + modifier: Modifier = Modifier, + onWatchVideoClick: (url: String) -> Unit, +) { + // TODO: switch color according to room type + Column(modifier = modifier.padding(8.dp)) { + Text( + text = "アーカイブ", + style = MaterialTheme.typography.titleLarge, + color = Color(0xFF45E761), + ) + Spacer(Modifier.height(8.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + timetableItem.asset.slideUrl?.let { slideUrl -> + Button( + modifier = Modifier.weight(1f), + onClick = { onViewSlideClick(slideUrl) }, + colors = ButtonDefaults.buttonColors().copy( + containerColor = Color(0xFF45E761), + ), + ) { + Icon( + imageVector = Icons.Outlined.Description, + contentDescription = "Slide", + ) + Text( + text = "スライド", + style = MaterialTheme.typography.labelLarge, + ) + } + } + timetableItem.asset.videoUrl?.let { videoUrl -> + Spacer(modifier = Modifier.width(8.dp)) + Button( + modifier = Modifier.weight(1f), + onClick = { onWatchVideoClick(videoUrl) }, + colors = ButtonDefaults.buttonColors().copy( + containerColor = Color(0xFF45E761), + ), + ) { + Icon( + imageVector = Icons.Outlined.PlayCircle, + contentDescription = "Video", + ) + Text( + text = "動画", + style = MaterialTheme.typography.labelLarge, + ) + } + } + } + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimeTableItemDetailContentPreview() { + KaigiTheme { + Surface { + TimeTableItemDetailContent( + timetableItem = Session.fake(), + currentLang = Lang.JAPANESE, + onLinkClick = {}, + ) + } + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimeTableItemDetailContentWithEnglishPreview() { + KaigiTheme { + Surface { + TimeTableItemDetailContent( + timetableItem = Session.fake(), + currentLang = Lang.ENGLISH, + onLinkClick = {}, + ) + } + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimeTableItemDetailContentWithMixedPreview() { + KaigiTheme { + Surface { + TimeTableItemDetailContent( + timetableItem = Session.fake(), + currentLang = Lang.MIXED, + onLinkClick = {}, + ) + } + } +} 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 new file mode 100644 index 000000000..ffc95dde3 --- /dev/null +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailHeadline.kt @@ -0,0 +1,101 @@ +package io.github.droidkaigi.confsched.sessions.component + +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.Row +import androidx.compose.foundation.layout.Spacer +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.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched.model.TimetableItem +import io.github.droidkaigi.confsched.model.fake +import io.github.droidkaigi.confsched.sessions.section.TagView +import io.github.droidkaigi.confsched.ui.rememberAsyncImagePainter + +@Composable +fun TimeTableItemDetailHeadline( + timetableItem: TimetableItem, + modifier: Modifier = Modifier, +) { + // TODO: switch color according to room type + Column( + modifier = modifier + .background(Color(0xFF132417)) + .padding(8.dp), + ) { + Row { + TagView( + tagText = timetableItem.room.name.currentLangTitle, + tagColor = Color(0xFF45E761), + ) + timetableItem.language.labels.forEach { label -> + Spacer(modifier = Modifier.padding(4.dp)) + TagView( + tagText = label, + tagColor = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = timetableItem.title.currentLangTitle, + style = MaterialTheme.typography.headlineSmall, + ) + Spacer(modifier = Modifier.height(16.dp)) + timetableItem.speakers.forEach { speaker -> + Row { + Image( + painter = rememberAsyncImagePainter(speaker.iconUrl), + contentDescription = null, + modifier = Modifier + .border(border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.onSurfaceVariant), shape = CircleShape) + .clip(CircleShape) + .size(52.dp), + ) + Spacer(modifier = Modifier.width(8.dp)) + Column { + Text( + text = speaker.name, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + text = speaker.tagLine, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + } + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimeTableItemDetailHeadlinePreview() { + KaigiTheme { + Surface { + TimeTableItemDetailHeadline( + timetableItem = TimetableItem.Session.fake(), + ) + } + } +} diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailSummaryCard.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailSummaryCard.kt new file mode 100644 index 000000000..3f1b24769 --- /dev/null +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimeTableItemDetailSummaryCard.kt @@ -0,0 +1,138 @@ +package io.github.droidkaigi.confsched.sessions.component + +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.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Category +import androidx.compose.material.icons.outlined.Language +import androidx.compose.material.icons.outlined.LocationOn +import androidx.compose.material.icons.outlined.Schedule +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched.model.Locale +import io.github.droidkaigi.confsched.model.TimetableItem +import io.github.droidkaigi.confsched.model.fake +import io.github.droidkaigi.confsched.model.getDefaultLocale +import io.github.droidkaigi.confsched.model.nameAndFloor + +@Composable +fun TimeTableItemDetailSummaryCard( + timetableItem: TimetableItem, + modifier: Modifier = Modifier, +) { + // TODO: switch color according to room type + Column( + modifier = modifier + .padding( + start = 8.dp, + end = 8.dp, + top = 16.dp, + bottom = 8.dp, + ) + .drawBehind { + drawRoundRect( + color = Color(0xFF45E761), + style = Stroke( + width = 2f, + pathEffect = PathEffect.dashPathEffect(floatArrayOf(5f, 5f), 0f), + ), + cornerRadius = CornerRadius(4.dp.toPx()), + ) + } + .padding(12.dp), + ) { + SummaryCardRow( + modifier = Modifier.fillMaxWidth(), + imageVector = Icons.Outlined.Schedule, + contentDescription = "Schedule", + title = "日時", + description = timetableItem.formattedDateTimeString, + ) + Spacer(Modifier.height(8.dp)) + SummaryCardRow( + modifier = Modifier.fillMaxWidth(), + imageVector = Icons.Outlined.LocationOn, + contentDescription = "Location", + title = "場所", + description = timetableItem.room.nameAndFloor, + ) + Spacer(Modifier.height(8.dp)) + SummaryCardRow( + modifier = Modifier.fillMaxWidth(), + imageVector = Icons.Outlined.Language, + contentDescription = "Language", + title = "対応言語", + description = timetableItem.getSupportedLangString( + getDefaultLocale() == Locale.JAPAN, + ), + ) + Spacer(Modifier.height(8.dp)) + SummaryCardRow( + modifier = Modifier.fillMaxWidth(), + imageVector = Icons.Outlined.Category, + contentDescription = "Category", + title = "カテゴリ", + description = timetableItem.category.title.currentLangTitle, + ) + } +} + +@Composable +private fun SummaryCardRow( + imageVector: ImageVector, + contentDescription: String, + title: String, + description: String, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon(imageVector = imageVector, contentDescription = contentDescription, tint = Color(0xFF45E761)) + Spacer(Modifier.width(8.dp)) + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + color = Color(0xFF45E761), + ) + Spacer(Modifier.width(8.dp)) + Text( + text = description, + style = MaterialTheme.typography.bodyMedium, + ) + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimeTableItemDetailSummaryCardPreview() { + KaigiTheme { + Surface { + TimeTableItemDetailSummaryCard( + timetableItem = TimetableItem.Session.fake(), + ) + } + } +} diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailBottomAppBar.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailBottomAppBar.kt new file mode 100644 index 000000000..1175aaa74 --- /dev/null +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailBottomAppBar.kt @@ -0,0 +1,98 @@ +package io.github.droidkaigi.confsched.sessions.component + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import conference_app_2024.feature.sessions.generated.resources.Res +import conference_app_2024.feature.sessions.generated.resources.calendar_add_on +import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched.model.TimetableItem +import io.github.droidkaigi.confsched.model.fake +import io.github.droidkaigi.confsched.sessions.TimetableItemDetailBookmarkIconTestTag +import org.jetbrains.compose.resources.painterResource + +@Composable +fun TimetableItemDetailBottomAppBar( + timetableItem: TimetableItem, + isBookmarked: Boolean, + onBookmarkClick: (TimetableItem) -> Unit, + onCalendarRegistrationClick: (TimetableItem) -> Unit, + modifier: Modifier = Modifier, + onShareClick: (TimetableItem) -> Unit, +) { + BottomAppBar( + modifier = modifier, + actions = { + IconButton(onClick = { onShareClick(timetableItem) }) { + Icon( + imageVector = Icons.Outlined.Share, + contentDescription = "Share", + ) + } + IconButton(onClick = { onCalendarRegistrationClick(timetableItem) }) { + Icon( + painter = painterResource(Res.drawable.calendar_add_on), + contentDescription = "Calendar", + ) + } + }, + floatingActionButton = { + FloatingActionButton( + modifier = Modifier.testTag(TimetableItemDetailBookmarkIconTestTag), + onClick = { onBookmarkClick(timetableItem) }, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + ) { + Icon( + imageVector = if (isBookmarked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = "Bookmarked", + ) + } + }, + ) +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimetableItemDetailBottomAppBarPreview() { + KaigiTheme { + Surface { + TimetableItemDetailBottomAppBar( + timetableItem = TimetableItem.Session.fake(), + isBookmarked = false, + onBookmarkClick = {}, + onCalendarRegistrationClick = {}, + onShareClick = {}, + ) + } + } +} + +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimetableItemDetailBottomAppBarBookmarkedPreview() { + KaigiTheme { + Surface { + TimetableItemDetailBottomAppBar( + timetableItem = TimetableItem.Session.fake(), + isBookmarked = true, + onBookmarkClick = {}, + onCalendarRegistrationClick = {}, + onShareClick = {}, + ) + } + } +} 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 new file mode 100644 index 000000000..1585e26a6 --- /dev/null +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/component/TimetableItemDetailTopAppBar.kt @@ -0,0 +1,132 @@ +package io.github.droidkaigi.confsched.sessions.component + +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 +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 androidx.compose.ui.graphics.Color +import io.github.droidkaigi.confsched.designsystem.preview.MultiLanguagePreviews +import io.github.droidkaigi.confsched.designsystem.preview.MultiThemePreviews +import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme +import io.github.droidkaigi.confsched.model.Lang + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TimetableItemDetailTopAppBar( + isLangSelectable: Boolean, + onNavigationIconClick: () -> Unit, + onSelectedLanguage: (Lang) -> Unit, + scrollBehavior: TopAppBarScrollBehavior, + modifier: Modifier = Modifier, +) { + // TODO: switch color according to room type + TopAppBar( + modifier = modifier, + colors = TopAppBarDefaults.topAppBarColors().copy( + containerColor = Color(0xFF132417), + scrolledContainerColor = Color(0xFF132417), + ), + title = {}, + navigationIcon = { + IconButton(onClick = { onNavigationIconClick() }) { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = "Back", + ) + } + }, + 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 = "日本語", + style = MaterialTheme.typography.bodySmall, + ) + }, + onClick = { + onSelectedLanguage(Lang.JAPANESE) + expanded = false + }, + ) + DropdownMenuItem( + text = { + Text( + text = "English", + style = MaterialTheme.typography.bodySmall, + ) + }, + onClick = { + onSelectedLanguage(Lang.ENGLISH) + expanded = false + }, + ) + } + } + }, + scrollBehavior = scrollBehavior, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimetableItemDetailTopAppBarPreview() { + KaigiTheme { + Surface { + TimetableItemDetailTopAppBar( + isLangSelectable = true, + onNavigationIconClick = {}, + onSelectedLanguage = {}, + scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@MultiThemePreviews +@MultiLanguagePreviews +fun TimetableItemDetailTopAppBarUnSelectablePreview() { + KaigiTheme { + Surface { + TimetableItemDetailTopAppBar( + isLangSelectable = false, + onNavigationIconClick = {}, + onSelectedLanguage = {}, + scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), + ) + } + } +}