diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched2023/AppModule.kt b/app-android/src/main/java/io/github/droidkaigi/confsched2023/AppModule.kt index 06b477e0d..1b555fd33 100644 --- a/app-android/src/main/java/io/github/droidkaigi/confsched2023/AppModule.kt +++ b/app-android/src/main/java/io/github/droidkaigi/confsched2023/AppModule.kt @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.droidkaigi.confsched2023.data.di.AppAndroidBuildConfig import io.github.droidkaigi.confsched2023.model.BuildConfigProvider +import kotlinx.datetime.Clock import javax.inject.Singleton @InstallIn(SingletonComponent::class) @@ -15,9 +16,19 @@ class AppModule { @Singleton @AppAndroidBuildConfig fun provideBuildConfigProvider(): BuildConfigProvider = AppBuildConfigProvider() + + @Provides + @Singleton + fun provideClockProvider(): ClockProvider = object : ClockProvider { + override fun clock(): Clock = Clock.System + } } class AppBuildConfigProvider( override val versionName: String = BuildConfig.VERSION_NAME, override val debugBuild: Boolean = BuildConfig.DEBUG, ) : BuildConfigProvider + +interface ClockProvider { + fun clock(): Clock +} diff --git a/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt b/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt index 83aff3ba3..e7d5bb352 100644 --- a/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt +++ b/app-android/src/main/java/io/github/droidkaigi/confsched2023/MainActivity.kt @@ -10,6 +10,7 @@ import androidx.activity.enableEdgeToEdge import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember @@ -19,12 +20,18 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.window.layout.DisplayFeature import androidx.window.layout.WindowInfoTracker import dagger.hilt.android.AndroidEntryPoint +import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + + @Inject + lateinit var clockProvider: ClockProvider + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -55,10 +62,12 @@ class MainActivity : ComponentActivity() { setContent { val windowSize = calculateWindowSizeClass(this) val displayFeatures = calculateDisplayFeatures(this) - KaigiApp( - windowSize = windowSize, - displayFeatures = displayFeatures, - ) + CompositionLocalProvider(LocalClock provides clockProvider.clock()) { + KaigiApp( + windowSize = windowSize, + displayFeatures = displayFeatures, + ) + } } } } diff --git a/app-android/src/test/java/io/github/droidkaigi/confsched2023/FakeAppModule.kt b/app-android/src/test/java/io/github/droidkaigi/confsched2023/FakeAppModule.kt new file mode 100644 index 000000000..90ddc1f3b --- /dev/null +++ b/app-android/src/test/java/io/github/droidkaigi/confsched2023/FakeAppModule.kt @@ -0,0 +1,24 @@ +package io.github.droidkaigi.confsched2023 + +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import javax.inject.Singleton + +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [AppModule::class], +) +@Module +class FakeAppModule { + @Provides + @Singleton + fun provideClockProvider(): ClockProvider = object : ClockProvider { + override fun clock(): Clock = object : Clock { + override fun now() = Instant.parse("2023-09-14T10:00:00.00Z") + } + } +} diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/DroidKaigi2023Day.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/DroidKaigi2023Day.kt index ac3feab49..0648b9948 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/DroidKaigi2023Day.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched2023/model/DroidKaigi2023Day.kt @@ -81,14 +81,11 @@ public enum class DroidKaigi2023Day( /** * @return appropriate initial day for now */ - fun initialSelectedDay(isTest: Boolean = false): DroidKaigi2023Day { - // Timetable tab set initial tab with current date. - // To get the consistent test result, fix selected timetable tab to Day1 here. - if (isTest) return Day1 + fun initialSelectedDay(clock: Clock): DroidKaigi2023Day { val reversedEntries = entries.sortedByDescending { it.day } var selectedDay = reversedEntries.last() for (entry in reversedEntries) { - if (Clock.System.now() <= entry.end) selectedDay = entry + if (clock.now() <= entry.end) selectedDay = entry } return selectedDay } diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index 8016717c1..2d65923e5 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { implementation(projects.core.model) implementation(projects.core.designsystem) implementation(projects.core.data) + implementation(projects.core.ui) implementation(projects.feature.main) implementation(projects.feature.sessions) implementation(projects.feature.about) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableScreenRobot.kt index 1bea2ec3e..e68f65f2a 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched2023/testing/robot/TimetableScreenRobot.kt @@ -1,5 +1,6 @@ package io.github.droidkaigi.confsched2023.testing.robot +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.isRoot @@ -25,6 +26,8 @@ import io.github.droidkaigi.confsched2023.sessions.component.TimetableUiTypeChan import io.github.droidkaigi.confsched2023.sessions.section.TimetableTabTestTag import io.github.droidkaigi.confsched2023.testing.RobotTestRule import io.github.droidkaigi.confsched2023.testing.coroutines.runTestWithLogging +import io.github.droidkaigi.confsched2023.ui.compositionlocal.FakeClock +import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock import kotlinx.coroutines.test.TestDispatcher import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -50,12 +53,14 @@ class TimetableScreenRobot @Inject constructor( fun setupTimetableScreenContent() { composeTestRule.setContent { - KaigiTheme { - TimetableScreen( - onSearchClick = { }, - onTimetableItemClick = { }, - onBookmarkIconClick = { }, - ) + CompositionLocalProvider(LocalClock provides FakeClock) { + KaigiTheme { + TimetableScreen( + onSearchClick = { }, + onTimetableItemClick = { }, + onBookmarkIconClick = { }, + ) + } } } waitUntilIdle() diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index a396e8521..48e34b842 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -18,6 +18,7 @@ kotlin { implementation(libs.kermit) api(projects.core.common) api(libs.composeImageLoader) + api(libs.kotlinxDatetime) } } } diff --git a/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/compositionlocal/LocalClock.kt b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/compositionlocal/LocalClock.kt new file mode 100644 index 000000000..0cb30ce7f --- /dev/null +++ b/core/ui/src/androidMain/kotlin/io/github/droidkaigi/confsched2023/ui/compositionlocal/LocalClock.kt @@ -0,0 +1,14 @@ +package io.github.droidkaigi.confsched2023.ui.compositionlocal + +import androidx.compose.runtime.staticCompositionLocalOf +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant + +@Suppress("CompositionLocalAllowlist") +val LocalClock = staticCompositionLocalOf { + Clock.System +} + +object FakeClock : Clock { + override fun now(): Instant = Instant.parse("2023-09-14T10:00:00.000Z") +} diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableScreen.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableScreen.kt index 545dc4330..1a48a09d1 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableScreen.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/TimetableScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -51,6 +52,8 @@ import io.github.droidkaigi.confsched2023.sessions.section.TimetableListUiState import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheet import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState import io.github.droidkaigi.confsched2023.ui.SnackbarMessageEffect +import io.github.droidkaigi.confsched2023.ui.compositionlocal.FakeClock +import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock import kotlinx.collections.immutable.toPersistentMap import kotlin.math.roundToInt @@ -249,32 +252,34 @@ private fun TimetableScreen( @MultiThemePreviews @Composable fun PreviewTimetableScreenDark() { - KaigiTheme { - TimetableScreen( - uiState = TimetableScreenUiState( - contentUiState = TimetableSheetUiState.ListTimetable( - mapOf( - DroidKaigi2023Day.Day1 to TimetableListUiState( - mapOf>().toPersistentMap(), - Timetable(), - ), - DroidKaigi2023Day.Day2 to TimetableListUiState( - mapOf>().toPersistentMap(), - Timetable(), + CompositionLocalProvider(LocalClock provides FakeClock) { + KaigiTheme { + TimetableScreen( + uiState = TimetableScreenUiState( + contentUiState = TimetableSheetUiState.ListTimetable( + mapOf( + DroidKaigi2023Day.Day1 to TimetableListUiState( + mapOf>().toPersistentMap(), + Timetable(), + ), + DroidKaigi2023Day.Day2 to TimetableListUiState( + mapOf>().toPersistentMap(), + Timetable(), + ), ), ), + timetableUiType = TimetableUiType.Grid, + onBookmarkIconClickStatus = false, ), - timetableUiType = TimetableUiType.Grid, - onBookmarkIconClickStatus = false, - ), - snackbarHostState = SnackbarHostState(), - onTimetableItemClick = {}, - onBookmarkClick = { _, _ -> }, - onBookmarkIconClick = {}, - onSearchClick = {}, - onTimetableUiChangeClick = {}, - onReachAnimationEnd = {}, - modifier = Modifier.statusBarsPadding(), - ) + snackbarHostState = SnackbarHostState(), + onTimetableItemClick = {}, + onBookmarkClick = { _, _ -> }, + onBookmarkIconClick = {}, + onSearchClick = {}, + onTimetableUiChangeClick = {}, + onReachAnimationEnd = {}, + modifier = Modifier.statusBarsPadding(), + ) + } } } diff --git a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableSheet.kt b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableSheet.kt index f2e208f73..71925d7b3 100644 --- a/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableSheet.kt +++ b/feature/sessions/src/main/java/io/github/droidkaigi/confsched2023/sessions/section/TimetableSheet.kt @@ -36,7 +36,7 @@ import io.github.droidkaigi.confsched2023.sessions.component.rememberTimetableTa import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.Empty import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.GridTimetable import io.github.droidkaigi.confsched2023.sessions.section.TimetableSheetUiState.ListTimetable -import io.github.droidkaigi.confsched2023.ui.isTest +import io.github.droidkaigi.confsched2023.ui.compositionlocal.LocalClock const val TimetableTabTestTag = "TimetableTab" @@ -60,7 +60,8 @@ fun TimetableSheet( contentPadding: PaddingValues, modifier: Modifier = Modifier, ) { - var selectedDay by rememberSaveable { mutableStateOf(DroidKaigi2023Day.initialSelectedDay(isTest())) } + val clock = LocalClock.current + var selectedDay by rememberSaveable { mutableStateOf(DroidKaigi2023Day.initialSelectedDay(clock)) } val corner by animateIntAsState( if (timetableScreenScrollState.isScreenLayoutCalculating || timetableScreenScrollState.isSheetExpandable) 40 else 0, label = "Timetable corner state",