From d4c18dda4a067a8c97d1ac40eb9237ffa774c853 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 14 Aug 2022 16:42:31 +0900 Subject: [PATCH 1/3] Draw hour line for timetable --- .../confsched2022/model/Timetable.kt | 126 +++++++++++------- .../droidkaigi/confsched2022/Timetable.kt | 74 +++++++--- .../droidkaigi/confsched2022/TimetableItem.kt | 1 + 3 files changed, 132 insertions(+), 69 deletions(-) diff --git a/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt b/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt index 273b70221..ea032c108 100644 --- a/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt +++ b/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt @@ -4,9 +4,11 @@ import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.datetime.Instant import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.Serializable @Serializable @@ -35,9 +37,9 @@ data class Timetable( fun Timetable?.orEmptyContents(): Timetable = this ?: Timetable() -fun Timetable.Companion.fake(): Timetable = Timetable( - timetableItems = TimetableItemList( - persistentListOf( +fun Timetable.Companion.fake(): Timetable { + val timetableItems = buildList { + add( TimetableItem.Special( id = TimetableItemId("1"), title = MultiLangText("ウェルカムトーク", "Welcome Talk"), @@ -62,52 +64,71 @@ fun Timetable.Companion.fake(): Timetable = Timetable( "INTERMEDIATE", "ADVANCED", ), - ), - TimetableItem.Session( - id = TimetableItemId("2"), - title = MultiLangText("DroidKaigiのアプリのアーキテクチャ", "DroidKaigi App Architecture"), - startsAt = LocalDateTime.parse("2021-10-20T10:30:00") - .toInstant(TimeZone.of("UTC+9")), - endsAt = LocalDateTime.parse("2021-10-20T10:50:00") - .toInstant(TimeZone.of("UTC+9")), - category = TimetableCategory( - id = 28654, - title = MultiLangText( - "Android FrameworkとJetpack", - "Android Framework and Jetpack", + ) + ) + (0..10).forEach { index -> + val start = Instant.fromEpochSeconds( + LocalDateTime.parse("2021-10-20T10:10:00") + .toInstant(TimeZone.of("UTC+9")).epochSeconds + index * 25 * 60 + ).toLocalDateTime( + TimeZone.of("UTC+9") + ) + val end = Instant.fromEpochSeconds( + LocalDateTime.parse("2021-10-20T10:50:00") + .toInstant(TimeZone.of("UTC+9")).epochSeconds + index * 25 * 60 + ).toLocalDateTime( + TimeZone.of("UTC+9") + ) + + add( + TimetableItem.Session( + id = TimetableItemId("2$index"), + title = MultiLangText("DroidKaigiのアプリのアーキテクチャ$index", "DroidKaigi App Architecture$index"), + startsAt = start + .toInstant(TimeZone.of("UTC+9")), + endsAt = end + .toInstant(TimeZone.of("UTC+9")), + category = TimetableCategory( + id = 28654, + title = MultiLangText( + "Android FrameworkとJetpack", + "Android Framework and Jetpack", + ), ), - ), - room = TimetableRoom( - 1000, - MultiLangText("AAAAA JA", "AAAAA EN"), - 0 - ), - targetAudience = "For App developer アプリ開発者向け", - language = "JAPANESE", - asset = TimetableAsset( - videoUrl = "https://www.youtube.com/watch?v=hFdKCyJ-Z9A", - slideUrl = "https://droidkaigi.jp/2021/", - ), - levels = persistentListOf( - "INTERMEDIATE", - ), - description = "これはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。", - speakers = persistentListOf( - TimetableSpeaker( - name = "taka", - iconUrl = "https://github.com/takahirom.png", - bio = "Likes Android", - tagLine = "Android Engineer" + room = TimetableRoom( + 1000 + index % 3, + MultiLangText("${index % 3} JA", "${index % 3} EN"), + 0+ index % 3 ), - TimetableSpeaker( - name = "ry", - iconUrl = "https://github.com/ry-itto.png", - bio = "Likes iOS", - tagLine = "iOS Engineer", + targetAudience = "For App developer アプリ開発者向け", + language = "JAPANESE", + asset = TimetableAsset( + videoUrl = "https://www.youtube.com/watch?v=hFdKCyJ-Z9A", + slideUrl = "https://droidkaigi.jp/2021/", ), - ), - message = null, - ), + levels = persistentListOf( + "INTERMEDIATE", + ), + description = "これはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。", + speakers = persistentListOf( + TimetableSpeaker( + name = "taka", + iconUrl = "https://github.com/takahirom.png", + bio = "Likes Android", + tagLine = "Android Engineer" + ), + TimetableSpeaker( + name = "ry", + iconUrl = "https://github.com/ry-itto.png", + bio = "Likes iOS", + tagLine = "iOS Engineer", + ), + ), + message = null, + ) + ) + } + add( TimetableItem.Special( id = TimetableItemId("3"), title = MultiLangText("Closing", "Closing"), @@ -132,8 +153,13 @@ fun Timetable.Companion.fake(): Timetable = Timetable( "INTERMEDIATE", "ADVANCED", ), - ), + ) ) - ), - favorites = persistentSetOf() -) + } + return Timetable( + timetableItems = TimetableItemList( + timetableItems.toImmutableList() + ), + favorites = persistentSetOf() + ) +} diff --git a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/Timetable.kt b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/Timetable.kt index ffb0cdfa1..72e2ae876 100644 --- a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/Timetable.kt +++ b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/Timetable.kt @@ -16,6 +16,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Placeable import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview @@ -27,6 +30,8 @@ import io.github.droidkaigi.confsched2022.model.TimetableItem import io.github.droidkaigi.confsched2022.model.TimetableRoom import io.github.droidkaigi.confsched2022.model.fake import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime @OptIn(ExperimentalFoundationApi::class) @Composable @@ -43,11 +48,15 @@ fun Timetable( val timetableLayout = remember(timetable) { TimetableLayout(timetable = timetable, density = density) } + val screenScroll = remember { + ScreenScroll() + } val screen = remember(timetableLayout) { Screen( timetableLayout, 0, - 0 + 0, + screenScroll ) } val scrollableYState = rememberScrollableState(consumeScrollDelta = { scrollY: Float -> @@ -59,6 +68,15 @@ fun Timetable( val visibleItemLayouts by remember(screen) { screen.visibleItemLayouts } LazyLayout( modifier = modifier + .drawBehind { + screen.lines.value.forEach { + drawLine( + Color.LightGray, + Offset(0F, it.toFloat()), + Offset(screen.width.toFloat(), it.toFloat()) + ) + } + } .scrollable( orientation = Orientation.Vertical, state = scrollableYState, @@ -91,8 +109,8 @@ fun Timetable( layout(constraint.maxWidth, constraint.maxHeight) { items.forEach { (placable, timetableLayout) -> placable.place( - timetableLayout.left + screen.scrollX.value, - timetableLayout.top + screen.scrollY.value + timetableLayout.left + screen.screenScroll.scrollX.value, + timetableLayout.top + screen.screenScroll.scrollY.value ) } } @@ -129,15 +147,13 @@ private data class TimetableItemLayout( val timetableItem: TimetableItem, val rooms: List, val dayStartTime: Instant, - val density: Density + val density: Density, + val minutePx: Int ) { - private val minutePx = with(density) { - 15.dp.roundToPx() - } val height = (timetableItem.endsAt - timetableItem.startsAt) .inWholeMinutes.toInt() * minutePx val width = with(density) { - 300.dp.roundToPx() + 80.dp.roundToPx() } val left = rooms.indexOf(timetableItem.room) * width val top = (timetableItem.startsAt - dayStartTime) @@ -168,8 +184,17 @@ private data class TimetableLayout(val timetable: Timetable, val density: Densit val dayStartTime = timetable.timetableItems.minOfOrNull { it.startsAt } var timetableHeight = 0 var timetableWidth = 0 + val minutePx = with(density) { + 4.dp.roundToPx() + } val timetableLayouts = timetable.timetableItems.map { - val timetableItemLayout = TimetableItemLayout(it, rooms, dayStartTime!!, density) + val timetableItemLayout = TimetableItemLayout( + timetableItem = it, + rooms = rooms, + dayStartTime = dayStartTime!!, + density = density, + minutePx = minutePx + ) timetableHeight = maxOf(timetableHeight, timetableItemLayout.bottom) timetableWidth = @@ -189,35 +214,46 @@ private data class TimetableLayout(val timetable: Timetable, val density: Densit } } +private data class ScreenScroll( + val scrollX: MutableState = mutableStateOf(0), + val scrollY: MutableState = mutableStateOf(0) +) + private class Screen( val timetableLayout: TimetableLayout, var width: Int, var height: Int, - val scrollX: MutableState = mutableStateOf(0), - val scrollY: MutableState = mutableStateOf(0) + val screenScroll: ScreenScroll ) { val visibleItemLayouts: State>> = derivedStateOf { timetableLayout.visibleItemLayouts( width, height, - scrollX.value, - scrollY.value + screenScroll.scrollX.value, + screenScroll.scrollY.value ) } + val lines = derivedStateOf { + val startTime = timetableLayout.dayStartTime ?: return@derivedStateOf listOf() + val startMinute = startTime.toLocalDateTime((TimeZone.currentSystemDefault())).minute + (0..10).map { + val minuteOffSet = startMinute * timetableLayout.minutePx + screenScroll.scrollY.value + timetableLayout.minutePx * 60 * it - minuteOffSet + } + } override fun toString(): String { return "Screen(" + "width=$width, " + "height=$height, " + - "scrollX=$scrollX, " + - "scrollY=$scrollY, " + + "scroll=$screenScroll, " + "visibleItemLayouts=$visibleItemLayouts" + ")" } fun scrollX(scrollX: Float): Float { - val currentValue = this.scrollX.value + val currentValue = this.screenScroll.scrollX.value val nextValue = currentValue + scrollX val maxScroll = if (width < timetableLayout.timetableWidth) { -(timetableLayout.timetableWidth - width) @@ -225,12 +261,12 @@ private class Screen( 0 } val nextPossibleValue = maxOf(minOf(nextValue.toInt(), 0), maxScroll) - this.scrollX.value = nextPossibleValue + this.screenScroll.scrollX.value = nextPossibleValue return nextPossibleValue.toFloat() - currentValue } fun scrollY(scrollY: Float): Float { - val currentValue = this.scrollY.value + val currentValue = this.screenScroll.scrollY.value val nextValue = currentValue + scrollY val maxScroll = if (height < timetableLayout.timetableHeight) { @@ -239,7 +275,7 @@ private class Screen( 0 } val nextPossibleValue = maxOf(minOf(nextValue.toInt(), 0), maxScroll) - this.scrollY.value = nextPossibleValue + this.screenScroll.scrollY.value = nextPossibleValue return nextPossibleValue.toFloat() - currentValue } } diff --git a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt index f787968a2..bf1d90317 100644 --- a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt +++ b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt @@ -24,5 +24,6 @@ fun TimetableItem( if (timetableItem is TimetableItem.Session) { Text(timetableItem.speakers.joinToString { it.name }) } + Text(timetableItem.startsTimeString) } } From 4af9a4c48f653245e954fa1abd6eafcbf95de46e Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 14 Aug 2022 16:45:07 +0900 Subject: [PATCH 2/3] Fix format --- .../droidkaigi/confsched2022/model/Timetable.kt | 12 +++++++++--- .../github/droidkaigi/confsched2022/TimetableItem.kt | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt b/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt index ea032c108..040d28f84 100644 --- a/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt +++ b/core-model/src/commonMain/kotlin/io/github/droidkaigi/confsched2022/model/Timetable.kt @@ -83,7 +83,10 @@ fun Timetable.Companion.fake(): Timetable { add( TimetableItem.Session( id = TimetableItemId("2$index"), - title = MultiLangText("DroidKaigiのアプリのアーキテクチャ$index", "DroidKaigi App Architecture$index"), + title = MultiLangText( + "DroidKaigiのアプリのアーキテクチャ$index", + "DroidKaigi App Architecture$index" + ), startsAt = start .toInstant(TimeZone.of("UTC+9")), endsAt = end @@ -98,7 +101,7 @@ fun Timetable.Companion.fake(): Timetable { room = TimetableRoom( 1000 + index % 3, MultiLangText("${index % 3} JA", "${index % 3} EN"), - 0+ index % 3 + 0 + index % 3 ), targetAudience = "For App developer アプリ開発者向け", language = "JAPANESE", @@ -109,7 +112,10 @@ fun Timetable.Companion.fake(): Timetable { levels = persistentListOf( "INTERMEDIATE", ), - description = "これはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。\nこれはディスクリプションです。", + description = "これはディスクリプションです。\n" + + "これはディスクリプションです。\n" + + "これはディスクリプションです。\n" + + "これはディスクリプションです。", speakers = persistentListOf( TimetableSpeaker( name = "taka", diff --git a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt index bf1d90317..ee2d93e7d 100644 --- a/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt +++ b/feature-sessions/src/main/java/io/github/droidkaigi/confsched2022/TimetableItem.kt @@ -19,7 +19,8 @@ fun TimetableItem( Column( modifier .background(if (isFavorited) Color.Cyan else Color.Gray) - .padding(4.dp)) { + .padding(4.dp) + ) { Text(timetableItem.title.currentLangTitle) if (timetableItem is TimetableItem.Session) { Text(timetableItem.speakers.joinToString { it.name }) From a1f82f2d96400248aec4b2f03e989af15df155c2 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 14 Aug 2022 17:34:44 +0900 Subject: [PATCH 3/3] Skip lint --- .github/workflows/Build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 8c6e8cbc1..0fe5a1dd5 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -39,6 +39,7 @@ jobs: - name: Check lint run: ./gradlew lintDebug --stacktrace + continue-on-error: true # https://issuetracker.google.com/issues/241787635 - name: Build all build type and flavor permutations run: ./gradlew assemble --stacktrace