Skip to content

Commit

Permalink
Merge pull request #48 from DroidKaigi/draw-hour-line
Browse files Browse the repository at this point in the history
Draw hour lines for timetable
  • Loading branch information
takahirom authored Aug 14, 2022
2 parents 2e4ecee + a1f82f2 commit 59e2cb9
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 69 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
Expand All @@ -62,52 +64,77 @@ 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,
)
)
(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(
"Android FrameworkとJetpack",
"Android Framework and Jetpack",
"DroidKaigiのアプリのアーキテクチャ$index",
"DroidKaigi App Architecture$index"
),
),
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"
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",
),
),
TimetableSpeaker(
name = "ry",
iconUrl = "https://github.com/ry-itto.png",
bio = "Likes iOS",
tagLine = "iOS Engineer",
room = TimetableRoom(
1000 + index % 3,
MultiLangText("${index % 3} JA", "${index % 3} EN"),
0 + index % 3
),
),
message = null,
),
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"
),
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"),
Expand All @@ -132,8 +159,13 @@ fun Timetable.Companion.fake(): Timetable = Timetable(
"INTERMEDIATE",
"ADVANCED",
),
),
)
)
),
favorites = persistentSetOf()
)
}
return Timetable(
timetableItems = TimetableItemList(
timetableItems.toImmutableList()
),
favorites = persistentSetOf()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 ->
Expand All @@ -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,
Expand Down Expand Up @@ -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
)
}
}
Expand Down Expand Up @@ -129,15 +147,13 @@ private data class TimetableItemLayout(
val timetableItem: TimetableItem,
val rooms: List<TimetableRoom>,
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)
Expand Down Expand Up @@ -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 =
Expand All @@ -189,48 +214,59 @@ private data class TimetableLayout(val timetable: Timetable, val density: Densit
}
}

private data class ScreenScroll(
val scrollX: MutableState<Int> = mutableStateOf(0),
val scrollY: MutableState<Int> = mutableStateOf(0)
)

private class Screen(
val timetableLayout: TimetableLayout,
var width: Int,
var height: Int,
val scrollX: MutableState<Int> = mutableStateOf(0),
val scrollY: MutableState<Int> = mutableStateOf(0)
val screenScroll: ScreenScroll
) {
val visibleItemLayouts: State<List<IndexedValue<TimetableItemLayout>>> =
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)
} else {
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) {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ 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 })
}
Text(timetableItem.startsTimeString)
}
}

0 comments on commit 59e2cb9

Please sign in to comment.