Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draw hour lines for timetable #48

Merged
merged 3 commits into from
Aug 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}