diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableRoom.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableRoom.kt index e8bc4b127..48919271b 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableRoom.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableRoom.kt @@ -1,5 +1,7 @@ package io.github.droidkaigi.confsched.model +import androidx.compose.ui.graphics.Color + @Immutable data class TimetableRooms(val rooms: List) @@ -15,6 +17,53 @@ data class TimetableRoom( } return sort.compareTo(other.sort) } + + fun getColor(): Color { + return when (name.enTitle) { + "Chipmunk" -> { + Color(0xFFFF974B) + } + "Dolphin" -> { + Color(0xFFBB85FF) + } + "Bumblebee" -> { + Color(0xFFDDD33C) + } + "Arctic Fox" -> { + Color(0xFF45E761) + } + else -> { + Color.White + } + } + } + + fun getShape(): Shapes { + return when (name.enTitle) { + "Arctic Fox" -> { + Shapes.SQUARE + } + "Bumblebee" -> { + Shapes.CIRCLE + } + "Chipmunk" -> { + Shapes.SHARP_DIAMOND + } + "Dolphin" -> { + Shapes.DIAMOND + } + else -> { + Shapes.SQUARE + } + } + } + + enum class Shapes { + SQUARE, + CIRCLE, + SHARP_DIAMOND, + DIAMOND, + } } val TimetableRoom.nameAndFloor: String diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index c03967267..a268a81e0 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(libs.daggerHiltAndroidTesting) implementation(libs.roborazzi) implementation(libs.kermit) + implementation(libs.coilTest) api(projects.core.testingManifest) api(libs.composeNavigation) api(libs.roborazziRule) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt index 99e46b5ec..7c1d24986 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt @@ -1,6 +1,7 @@ package io.github.droidkaigi.confsched.testing import android.content.Intent +import android.graphics.drawable.ColorDrawable import android.os.Bundle import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable @@ -12,6 +13,10 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.rules.ActivityScenarioRule import co.touchlab.kermit.CommonWriter import co.touchlab.kermit.Logger +import coil3.ImageLoader +import coil3.SingletonImageLoader +import coil3.test.FakeImageLoaderEngine +import coil3.test.default import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions import com.github.takahirom.roborazzi.RoborazziOptions.PixelBitConfig @@ -108,6 +113,18 @@ class RobotTestRule( ), ), ) + .around(object : TestWatcher() { + override fun starting(description: Description?) { + super.starting(description) + val engine = FakeImageLoaderEngine.Builder() + .default(ColorDrawable(android.graphics.Color.BLUE)) + .build() + val imageLoader = ImageLoader.Builder(ApplicationProvider.getApplicationContext()) + .components { add(engine) } + .build() + SingletonImageLoader.setUnsafe(imageLoader) + } + }) .around(composeTestRule) .apply(base, description) } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index e7f3ebe7b..39953d332 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -17,7 +17,7 @@ kotlin { implementation(projects.core.data) implementation(libs.kermit) api(projects.core.common) - api(libs.composeImageLoader) + api(libs.coil) api(libs.kotlinxDatetime) implementation(libs.moleculeRuntime) implementation(libs.coreBundle) diff --git a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ImagePainter.kt b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ImagePainter.kt index 40433ee72..6813d3287 100644 --- a/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ImagePainter.kt +++ b/core/ui/src/commonMain/kotlin/io/github/droidkaigi/confsched/ui/ImagePainter.kt @@ -2,9 +2,10 @@ package io.github.droidkaigi.confsched.ui import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter -import com.seiko.imageloader.rememberImagePainter @Composable fun rememberAsyncImagePainter(url: String): Painter { - return rememberImagePainter(url = url) + return coil3.compose.rememberAsyncImagePainter( + model = url, + ) } diff --git a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/section/TimetableList.kt b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/section/TimetableList.kt index 12e8df34b..c4a26f775 100644 --- a/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/section/TimetableList.kt +++ b/feature/sessions/src/commonMain/kotlin/io/github/droidkaigi/confsched/sessions/section/TimetableList.kt @@ -1,23 +1,51 @@ package io.github.droidkaigi.confsched.sessions.section +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +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.PaddingValues 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.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Circle +import androidx.compose.material.icons.filled.Diamond +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Square +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.filled.Thermostat +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import io.github.droidkaigi.confsched.model.Timetable import io.github.droidkaigi.confsched.model.TimetableItem +import io.github.droidkaigi.confsched.model.TimetableRoom.Shapes.CIRCLE +import io.github.droidkaigi.confsched.model.TimetableRoom.Shapes.DIAMOND +import io.github.droidkaigi.confsched.model.TimetableRoom.Shapes.SHARP_DIAMOND +import io.github.droidkaigi.confsched.model.TimetableRoom.Shapes.SQUARE import io.github.droidkaigi.confsched.sessions.TimetableListItemBookmarkIconTestTag import io.github.droidkaigi.confsched.sessions.TimetableListItemTestTag +import io.github.droidkaigi.confsched.ui.rememberAsyncImagePainter import kotlinx.collections.immutable.PersistentMap const val TimetableListTestTag = "TimetableList" @@ -39,28 +67,129 @@ fun TimetableList( LazyColumn( modifier = modifier.testTag(TimetableListTestTag), state = scrollState, + verticalArrangement = Arrangement.spacedBy(10.dp), contentPadding = contentPadding, ) { items(uiState.timetable.timetableItems, key = { it.id.value }) { timetableItem -> val isBookmarked = uiState.timetable.bookmarks.contains(timetableItem.id) - Row { + val roomName = timetableItem.room.name.currentLangTitle + + // TODO: Replace with the real icons. Probably need to embed them. + val roomIcon = when (timetableItem.room.getShape()) { + SQUARE -> { + Icons.Filled.Square + } + + CIRCLE -> { + Icons.Filled.Circle + } + + DIAMOND -> { + Icons.Filled.Thermostat + } + + SHARP_DIAMOND -> { + Icons.Filled.Diamond + } + + else -> { + Icons.Filled.Star + } + } + val roomColor = timetableItem.room.getColor() + + Column( + modifier = Modifier + .border( + border = BorderStroke(width = 1.dp, color = Color.White), + shape = RoundedCornerShape(5.dp), + ) + .padding(15.dp), + ) { + Row { + TagView(tagText = roomName, icon = roomIcon, tagColor = roomColor) + Spacer(modifier = Modifier.padding(3.dp)) + timetableItem.language.labels.forEach { label -> + TagView(tagText = label, tagColor = Color.White) + Spacer(modifier = Modifier.padding(3.dp)) + } + Spacer(modifier = Modifier.weight(1f)) + TextButton( + onClick = { onBookmarkClick(timetableItem, true) }, + modifier = Modifier.testTag(TimetableListItemBookmarkIconTestTag), + ) { + if (isBookmarked) { + Icon( + Icons.Filled.Favorite, + contentDescription = "Bookmarked", + tint = Color.Green, + ) + } else { + Icon( + Icons.Outlined.FavoriteBorder, + contentDescription = "Not Bookmarked", + tint = Color.White, + ) + } + } + } Text( text = timetableItem.title.currentLangTitle, + fontSize = 24.sp, modifier = Modifier - .padding(horizontal = 16.dp) .testTag(TimetableListItemTestTag) + .padding(bottom = 5.dp) .clickable { onTimetableItemClick(timetableItem) }, ) - Spacer(modifier = Modifier.weight(1f)) - Button( - modifier = Modifier.testTag(TimetableListItemBookmarkIconTestTag), - onClick = { onBookmarkClick(timetableItem, true) }, - ) { - Text( - text = "Bookmark $isBookmarked", - ) + timetableItem.speakers.forEach { speaker -> + Row { + // TODO: This style of image loading was included by default but it seems slow + val painter = rememberAsyncImagePainter(speaker.iconUrl) + Image( + painter = painter, + modifier = Modifier + .width(32.dp) + .height(32.dp) + .clip(CircleShape), + contentDescription = "image", + ) + Text( + text = speaker.name, + fontSize = 24.sp, + modifier = Modifier + .testTag(TimetableListItemTestTag) + .padding(5.dp) + .align(Alignment.CenterVertically), + ) + } } + // TODO: There is no data for the warning string right now. (Should go here) } } } } + +@Composable +fun TagView( + tagText: String, + tagColor: Color, + modifier: Modifier = Modifier, + icon: ImageVector? = null, +) { + Row( + modifier = modifier + .border(border = BorderStroke(width = 1.dp, color = tagColor)) + .clip(RoundedCornerShape(15.dp)) + .padding(5.dp), + ) { + icon?.let { ico -> + Icon(ico, "", tint = tagColor) + } + Spacer(modifier = Modifier.padding(3.dp)) + Text( + color = tagColor, + text = tagText, + modifier = Modifier.padding(end = 5.dp), + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20e2e2b9d..8981e64d2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ kover = "0.7.6" androidxLifecycleProcess = "2.8.2" skie = "0.8.2" composablePreviewScanner = "0.1.1" +coil = "3.0.0-alpha06" [libraries] androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -107,8 +108,8 @@ composeNavigation = { module = "org.jetbrains.androidx.navigation:navigation-com coreBundle = { module = "org.jetbrains.androidx.core:core-bundle", version = "1.0.0-alpha01" } composeHiltNavigtation = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "composeHiltNavigatiaon" } composeLintCheck = { module = "com.slack.lint.compose:compose-lint-checks", version = "1.3.1" } -composeCoil = { module = "io.coil-kt:coil-compose", version = "2.4.0" } -composeImageLoader = { module = "io.github.qdsfdhvh:image-loader", version = "1.6.7" } +coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coilTest = { module = "io.coil-kt.coil3:coil-test", version.ref = "coil" } composeShimmer = { module = "com.valentinilk.shimmer:compose-shimmer", version = "1.0.5" } rin = { module = "io.github.takahirom.rin:rin", version.ref = "rin" } lottieCompose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" }