From 6e093afb560882b6c888c42c857fde8aaa1de3d9 Mon Sep 17 00:00:00 2001 From: mtkw0127 Date: Wed, 10 Jul 2024 00:58:05 +0900 Subject: [PATCH] Implement event map --- .../data/eventmap/EventMapApiClient.kt | 3 +- .../data/eventmap/FakeEventMapApiClient.kt | 12 +- .../confsched/model/EventMapEvent.kt | 27 ++- feature/eventmap/build.gradle.kts | 2 + .../composeResources/drawable/event_map.xml | 188 ++++++++++++++++++ .../confsched/eventmap/EventMapScreen.kt | 32 ++- .../eventmap/component/EventMapItem.kt | 113 ++++++++++- .../eventmap/component/EventMapTab.kt | 106 ++++++++++ 8 files changed, 466 insertions(+), 17 deletions(-) create mode 100644 feature/eventmap/src/commonMain/composeResources/drawable/event_map.xml create mode 100644 feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapTab.kt diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/EventMapApiClient.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/EventMapApiClient.kt index dee8bc5e2..5fd44ff39 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/EventMapApiClient.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/EventMapApiClient.kt @@ -3,6 +3,7 @@ package io.github.droidkaigi.confsched.data.eventmap import de.jensklingenberg.ktorfit.http.GET import io.github.droidkaigi.confsched.data.eventmap.response.EventMapResponse import io.github.droidkaigi.confsched.model.EventMapEvent +import io.github.droidkaigi.confsched.model.createSampleEventMapEvent import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList @@ -17,5 +18,5 @@ public interface EventMapApiClient { } public fun EventMapResponse.toEventMapList(): PersistentList { - return listOf(EventMapEvent()).toPersistentList() + return listOf(createSampleEventMapEvent()).toPersistentList() } diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/FakeEventMapApiClient.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/FakeEventMapApiClient.kt index baa0036da..7864fc5bc 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/FakeEventMapApiClient.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/eventmap/FakeEventMapApiClient.kt @@ -2,7 +2,9 @@ package io.github.droidkaigi.confsched.data.eventmap import io.github.droidkaigi.confsched.data.eventmap.response.EventMapResponse import io.github.droidkaigi.confsched.model.EventMapEvent +import io.github.droidkaigi.confsched.model.createSampleEventMapEvent import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList import okio.IOException public class FakeEventMapApiClient : EventMapApiClient { @@ -10,7 +12,7 @@ public class FakeEventMapApiClient : EventMapApiClient { public sealed class Status : EventMapApiClient { public data object Operational : Status() { override suspend fun eventMapEvents(): PersistentList { - return EventMapResponse().toEventMapList() + return EventMapResponse().fake() } } @@ -31,3 +33,11 @@ public class FakeEventMapApiClient : EventMapApiClient { return status.eventMapEvents() } } + +public fun EventMapResponse.fake(): PersistentList { + return List(10) { + createSampleEventMapEvent( + isFavorite = it % 2 == 0 + ) + }.toPersistentList() +} diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapEvent.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapEvent.kt index 82dcba0e7..caf3d5484 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapEvent.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/EventMapEvent.kt @@ -1,3 +1,28 @@ package io.github.droidkaigi.confsched.model -class EventMapEvent +import kotlinx.datetime.LocalTime + +class EventMapEvent( + val name: String, + val roomName: String, + val dateLabel: String, + val isFavorite: Boolean, + val description: String, + private val startTime: LocalTime, + private val endTime: LocalTime, +) { + val timeDuration: String + get() = "$startTime ~ $endTime" +} + +fun createSampleEventMapEvent( + isFavorite: Boolean = false, +) = EventMapEvent( + name = "ランチミートアップ", + roomName = "Arctic Fox", + dateLabel = "DAT1", + isFavorite = isFavorite, + description = "様々なテーマごとに集まって、一緒にランチを食べながらお話ししましょう。席に限りがありますので、お弁当受け取り後お早めにお越しください。", + startTime = LocalTime(10, 30), + endTime = LocalTime(12, 30), +) diff --git a/feature/eventmap/build.gradle.kts b/feature/eventmap/build.gradle.kts index 42f680e8a..ff6c4f918 100644 --- a/feature/eventmap/build.gradle.kts +++ b/feature/eventmap/build.gradle.kts @@ -12,6 +12,8 @@ kotlin { implementation(libs.kotlinxCoroutinesCore) implementation(projects.core.designsystem) implementation(libs.moleculeRuntime) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) } } androidUnitTest { diff --git a/feature/eventmap/src/commonMain/composeResources/drawable/event_map.xml b/feature/eventmap/src/commonMain/composeResources/drawable/event_map.xml new file mode 100644 index 000000000..0cbed1640 --- /dev/null +++ b/feature/eventmap/src/commonMain/composeResources/drawable/event_map.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt index b77cda763..4eac633b4 100644 --- a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt +++ b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/EventMapScreen.kt @@ -1,10 +1,12 @@ package io.github.droidkaigi.confsched.eventmap +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +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.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons.AutoMirrored.Filled import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api @@ -22,6 +24,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder @@ -29,6 +33,7 @@ import androidx.navigation.compose.composable import co.touchlab.kermit.Logger import io.github.droidkaigi.confsched.compose.rememberEventEmitter import io.github.droidkaigi.confsched.eventmap.component.EventMapItem +import io.github.droidkaigi.confsched.eventmap.component.EventMapTab import io.github.droidkaigi.confsched.model.EventMapEvent import io.github.droidkaigi.confsched.ui.SnackbarMessageEffect import io.github.droidkaigi.confsched.ui.UserMessageStateHolder @@ -123,7 +128,7 @@ fun EventMapScreen( if (scrollBehavior != null) { LargeTopAppBar( title = { - Text(text = "EventMapEvent") + Text(text = "イベントマップ") }, navigationIcon = { IconButton( @@ -164,14 +169,31 @@ private fun EventMap( modifier: Modifier = Modifier, ) { LazyColumn( - modifier = modifier, + modifier = modifier.padding(horizontal = 16.dp), ) { - items(eventMapEvents) { + item { + EventMapTab() + Spacer(Modifier.height(26.dp)) + } + itemsIndexed(eventMapEvents) { index, eventMapEvent -> EventMapItem( - eventMapEvent = it, + eventMapEvent = eventMapEvent, onClick = onEventMapItemClick, + onClickFavorite = { /* TODO */ }, modifier = Modifier.fillMaxWidth(), ) + if (eventMapEvents.lastIndex != index) { + Spacer(modifier = Modifier.height(12.dp)) + } } } } + +@Composable +@Preview +fun PreviewEventMapScreen() { + EventMapScreen( + onNavigationIconClick = { }, + onEventMapItemClick = { }, + ) +} diff --git a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapItem.kt b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapItem.kt index f5d5cb167..a02770e93 100644 --- a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapItem.kt +++ b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapItem.kt @@ -1,16 +1,29 @@ package io.github.droidkaigi.confsched.eventmap.component +import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable 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.height import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import io.github.droidkaigi.confsched.model.EventMapEvent @Composable @@ -18,22 +31,104 @@ fun EventMapItem( eventMapEvent: EventMapEvent, @Suppress("UnusedParameter") onClick: (url: String) -> Unit, + onClickFavorite: (eventMapEvent: EventMapEvent) -> Unit, modifier: Modifier = Modifier, ) { - Row( + val green = Color(0xFF45E761) + val gray = Color(0xFFC5C7C4) + Column( modifier = modifier + .border(1.dp, gray, RoundedCornerShape(5.dp)) + .background(Color.Transparent, RoundedCornerShape(5.dp)) .clickable { // eventMapEvent.profileUrl?.let(onClick) } - .padding(horizontal = 16.dp, vertical = 10.dp), + .padding(12.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + ) { + ToolTip( + text = eventMapEvent.roomName, + icon = Icons.Filled.Star, + color = green + ) + Spacer(Modifier.width(4.dp)) + ToolTip( + text = eventMapEvent.dateLabel, + color = Color(0xFFBFC9C2) + ) + Spacer(Modifier.weight(1F)) + Icon( + imageVector = Icons.Default.Star, + contentDescription = null, + modifier = Modifier.size(24.dp).clickable { onClickFavorite(eventMapEvent) }, + tint = if (eventMapEvent.isFavorite) green else gray + ) + } + Spacer(Modifier.height(8.dp)) + Text( + text = eventMapEvent.name, + fontSize = 17.sp, + lineHeight = 23.8.sp, + fontWeight = FontWeight.W600, + letterSpacing = 0.1.sp, + color = gray + ) + Spacer(Modifier.height(8.dp)) + Text( + text = eventMapEvent.description, + fontSize = 13.sp, + lineHeight = 20.sp, + fontWeight = FontWeight.W400, + letterSpacing = 0.25.sp, + color = Color.White.copy(alpha = 0.7F) + ) + Spacer(Modifier.height(8.dp)) + Text( + text = eventMapEvent.timeDuration, + fontSize = 11.sp, + lineHeight = 15.sp, + fontWeight = FontWeight.W600, + letterSpacing = 0.1.sp, + color = green + ) + } +} + +@Composable +private fun ToolTip( + text: String, + icon: ImageVector? = null, + color: Color = Color(0xFFC5C7C4), + backgroundColor: Color = Color.Transparent, +) { + Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(23.dp), + modifier = Modifier + .border(1.dp, color) + .background(backgroundColor, RoundedCornerShape(0.dp)) + .padding(vertical = 4.5.dp, horizontal = 8.dp) ) { + icon?.let { + Icon( + imageVector = icon, + contentDescription = null, + tint = color, + modifier = Modifier.size(12.dp) + ) + Spacer(Modifier.width(3.dp)) + } Text( - text = "EventMapItem + $eventMapEvent", - style = MaterialTheme.typography.bodyLarge, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + text = text, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp, + fontWeight = FontWeight.W500, + color = color ) } } diff --git a/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapTab.kt b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapTab.kt new file mode 100644 index 000000000..6101819af --- /dev/null +++ b/feature/eventmap/src/commonMain/kotlin/io/github/droidkaigi/confsched/eventmap/component/EventMapTab.kt @@ -0,0 +1,106 @@ +package io.github.droidkaigi.confsched.eventmap.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.Text +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import conference_app_2024.feature.eventmap.generated.resources.Res +import conference_app_2024.feature.eventmap.generated.resources.event_map +import org.jetbrains.compose.resources.painterResource + +@Composable +fun EventMapTab() { + val selectedColor = Color(0xFF4AFF82) + var selectedTabIndex by rememberSaveable { mutableStateOf(0) } + + + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start, + ) { + TabRow( + modifier = Modifier.width(80.dp), + selectedTabIndex = selectedTabIndex, + indicator = @Composable { tabPositions -> + if (selectedTabIndex < tabPositions.size) { + TabRowDefaults.SecondaryIndicator( + modifier = Modifier + .tabIndicatorOffset(tabPositions[selectedTabIndex]), + color = selectedColor, + ) + } + }, + tabs = { + Tab( + modifier = Modifier.height(64.dp), + selected = true, + onClick = { + selectedTabIndex = 0 + }, + selectedContentColor = selectedColor, + unselectedContentColor = Color.White, + ) { + FloorText( + text = "1F", + isSelected = selectedTabIndex == 0, + ) + } + Tab( + modifier = Modifier.height(64.dp), + selected = false, + onClick = { + selectedTabIndex = 1 + }, + selectedContentColor = selectedColor, + unselectedContentColor = Color.White, + ) { + FloorText( + text = "2F", + isSelected = selectedTabIndex == 1, + ) + } + }, + ) + Spacer(modifier = Modifier.height(24.dp)) + Image( + painter = painterResource(Res.drawable.event_map), + contentDescription = null, + ) + } +} + +@Composable +private fun FloorText( + text: String, + isSelected: Boolean, +) { + Text( + text = text, + fontWeight = FontWeight.W400, + fontSize = 24.sp, + lineHeight = 23.8.sp, + color = if (isSelected) { + Color(0xFF4AFF82) + } else { + Color.White + }, + ) +}