From 9f8f73496989e38949bd9d81decf4ca34cb312cf Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:17:05 +0900 Subject: [PATCH 01/39] :sparkles: I added a Presenter to display a snack bar that asks for permission to access the calendar. --- .../shared/IosComposeKaigiAppPresenter.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt new file mode 100644 index 000000000..db59c70e0 --- /dev/null +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiAppPresenter.kt @@ -0,0 +1,34 @@ +package io.github.droidkaigi.confsched.shared + +import androidx.compose.runtime.Composable +import io.github.droidkaigi.confsched.compose.EventEffect +import io.github.droidkaigi.confsched.compose.EventFlow +import io.github.droidkaigi.confsched.droidkaigiui.providePresenterDefaults +import io.github.droidkaigi.confsched.shared.IosComposeKaigiAppEvent.ShowRequiresAuthorization + +sealed interface IosComposeKaigiAppEvent { + val snackbarMessage: String + + data class ShowRequiresAuthorization( + override val snackbarMessage: String, + ) : IosComposeKaigiAppEvent +} + +@Composable +fun iosComposeKaigiAppPresenter( + events: EventFlow +) : IosComposeKaigiAppUiState = providePresenterDefaults { userMessageStateHolder -> + EventEffect(events) { event -> + when (event) { + is ShowRequiresAuthorization -> { + userMessageStateHolder.showMessage( + message = event.snackbarMessage, + // TODO Add code to transition to the settings screen when the action button is pressed. + // TODO Perhaps UIApplication.openSettingsURLString can be used to achieve this. + actionLabel = null, + ) + } + } + } + IosComposeKaigiAppUiState(userMessageStateHolder) +} From 4ac9ab1497cf315a28d548645e7ef5c1bb39b803 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:18:06 +0900 Subject: [PATCH 02/39] :recycle: On the session details screen of Compose Multiplatform, a snack bar is now displayed when you do not have permission to access the calendar. --- .../confsched/shared/IosComposeKaigiApp.kt | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt index ccb434e71..e042be72d 100644 --- a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt @@ -1,8 +1,12 @@ package io.github.droidkaigi.confsched.shared +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass @@ -11,9 +15,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.ComposeUIViewController import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder @@ -23,12 +29,15 @@ import co.touchlab.kermit.Logger import io.github.droidkaigi.confsched.about.aboutScreen import io.github.droidkaigi.confsched.about.aboutScreenRoute import io.github.droidkaigi.confsched.about.navigateAboutScreen +import io.github.droidkaigi.confsched.compose.rememberEventFlow import io.github.droidkaigi.confsched.contributors.contributorsScreenRoute import io.github.droidkaigi.confsched.contributors.contributorsScreens import io.github.droidkaigi.confsched.data.Repositories import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme import io.github.droidkaigi.confsched.designsystem.theme.dotGothic16FontFamily import io.github.droidkaigi.confsched.droidkaigiui.NavHostWithSharedAxisX +import io.github.droidkaigi.confsched.droidkaigiui.SnackbarMessageEffect +import io.github.droidkaigi.confsched.droidkaigiui.UserMessageStateHolder import io.github.droidkaigi.confsched.eventmap.eventMapScreenRoute import io.github.droidkaigi.confsched.eventmap.eventMapScreens import io.github.droidkaigi.confsched.eventmap.navigateEventMapScreen @@ -91,15 +100,40 @@ import platform.darwin.NSObject private object ExternalNavControllerLink { var onLicenseScreenRequest: (() -> Unit)? = null + var onAccessCalendarIsDenied: (() -> Unit)? = null } +data class IosComposeKaigiAppUiState( + val userMessageStateHolder: UserMessageStateHolder, +) + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Suppress("UNUSED") fun kaigiAppController( repositories: Repositories, onLicenseScreenRequest: () -> Unit, ): UIViewController = ComposeUIViewController { - ExternalNavControllerLink.onLicenseScreenRequest = onLicenseScreenRequest + val eventFlow = rememberEventFlow() + val uiState = iosComposeKaigiAppPresenter(events = eventFlow) + + val snackbarHostState = remember { SnackbarHostState() } + + ExternalNavControllerLink.apply { + // TODO Use Kotlin Multiplatform Resources + val snackbarMessage = "Authorization is required to add sessions to the calendar." + + this.onLicenseScreenRequest = onLicenseScreenRequest + this.onAccessCalendarIsDenied = { + eventFlow.tryEmit(IosComposeKaigiAppEvent.ShowRequiresAuthorization( + snackbarMessage = snackbarMessage, + )) + } + } + + SnackbarMessageEffect( + snackbarHostState = snackbarHostState, + userMessageStateHolder = uiState.userMessageStateHolder, + ) CompositionLocalProvider( LocalRepositories provides repositories.map @@ -118,10 +152,18 @@ fun kaigiAppController( } Logging.initialize() - KaigiApp( - windowSize = windowSizeClass, - fontFamily = fontFamily, - ) + Box(modifier = Modifier.fillMaxSize()) { + KaigiApp( + windowSize = windowSizeClass, + fontFamily = fontFamily, + ) + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 108.dp) + ) + } } } @@ -346,8 +388,7 @@ private class ExternalNavController( eventStore.requestAccessToEntityType(EKEntityTypeEvent) { granted, error -> if (granted.not()) { - // TODO Display a message asking the user to add permissions. - // TODO Otherwise, the privileges will remain permanently denied. + ExternalNavControllerLink.onAccessCalendarIsDenied?.invoke() Logger.e("Calendar access was denied by the user.") return@requestAccessToEntityType } From e9f9cd95efc4503b745295d282ccca5c4d76e570 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:49:04 +0900 Subject: [PATCH 03/39] :sparkles: Compose Multiplatform Resource can now be used in the app-ios-shared module. --- app-ios-shared/build.gradle.kts | 1 + .../src/commonMain/composeResources/values-ja/strings.xml | 4 ++++ .../src/commonMain/composeResources/values/strings.xml | 4 ++++ .../github/droidkaigi/confsched/shared/AppIosSharedRes.kt | 8 ++++++++ 4 files changed, 17 insertions(+) create mode 100644 app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml create mode 100644 app-ios-shared/src/commonMain/composeResources/values/strings.xml create mode 100644 app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/AppIosSharedRes.kt diff --git a/app-ios-shared/build.gradle.kts b/app-ios-shared/build.gradle.kts index 065a7c1b3..cf0e0568e 100644 --- a/app-ios-shared/build.gradle.kts +++ b/app-ios-shared/build.gradle.kts @@ -79,6 +79,7 @@ kotlin { api(projects.feature.favorites) implementation(libs.kotlinxCoroutinesCore) implementation(libs.skieAnnotation) + implementation(compose.components.resources) } } iosTest { diff --git a/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml b/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml new file mode 100644 index 000000000..04ed06b08 --- /dev/null +++ b/app-ios-shared/src/commonMain/composeResources/values-ja/strings.xml @@ -0,0 +1,4 @@ + + + セッションを予定として追加するには、カレンダーへのアクセス権限が必要です。 + diff --git a/app-ios-shared/src/commonMain/composeResources/values/strings.xml b/app-ios-shared/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 000000000..b6e4f8499 --- /dev/null +++ b/app-ios-shared/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,4 @@ + + + To add a session as a scheduled event, you need access permission to the calendar. + diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/AppIosSharedRes.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/AppIosSharedRes.kt new file mode 100644 index 000000000..47880a585 --- /dev/null +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/AppIosSharedRes.kt @@ -0,0 +1,8 @@ +package io.github.droidkaigi.confsched.shared + +import conference_app_2024.app_ios_shared.generated.resources.Res + +object AppIosSharedRes { + val drawable = Res.drawable + val string = Res.string +} From 6a5c706bdb92fb8ce87b167743db2db64177d358 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 30 Aug 2024 08:49:56 +0900 Subject: [PATCH 04/39] :recycle: The message text in the snack bar now uses the string from the Compose Multiplatform Resource. --- .../github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt index e042be72d..35c0af575 100644 --- a/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt +++ b/app-ios-shared/src/commonMain/kotlin/io/github/droidkaigi/confsched/shared/IosComposeKaigiApp.kt @@ -26,6 +26,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import co.touchlab.kermit.Logger +import conference_app_2024.app_ios_shared.generated.resources.permission_required import io.github.droidkaigi.confsched.about.aboutScreen import io.github.droidkaigi.confsched.about.aboutScreenRoute import io.github.droidkaigi.confsched.about.navigateAboutScreen @@ -85,6 +86,7 @@ import io.github.droidkaigi.confsched.staff.staffScreenRoute import io.github.droidkaigi.confsched.staff.staffScreens import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.stringResource import platform.EventKit.EKEntityType.EKEntityTypeEvent import platform.EventKit.EKEvent import platform.EventKit.EKEventStore @@ -119,8 +121,7 @@ fun kaigiAppController( val snackbarHostState = remember { SnackbarHostState() } ExternalNavControllerLink.apply { - // TODO Use Kotlin Multiplatform Resources - val snackbarMessage = "Authorization is required to add sessions to the calendar." + val snackbarMessage = stringResource(AppIosSharedRes.string.permission_required) this.onLicenseScreenRequest = onLicenseScreenRequest this.onAccessCalendarIsDenied = { From a22c33e7ad71534235ea7f65eb1dd72d2cd201e4 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:15:47 +0900 Subject: [PATCH 05/39] :wrench: I restored the original because there was no need to add the dependency for compose.components.resources. --- app-ios-shared/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/app-ios-shared/build.gradle.kts b/app-ios-shared/build.gradle.kts index cf0e0568e..065a7c1b3 100644 --- a/app-ios-shared/build.gradle.kts +++ b/app-ios-shared/build.gradle.kts @@ -79,7 +79,6 @@ kotlin { api(projects.feature.favorites) implementation(libs.kotlinxCoroutinesCore) implementation(libs.skieAnnotation) - implementation(compose.components.resources) } } iosTest { From db3f5264d13cec1f32389f03a78d2dd60ad880c7 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Sat, 31 Aug 2024 02:57:57 +0900 Subject: [PATCH 06/39] :wrench: ./gradlew detekt --auto-correct --- .../droidkaigi/confsched/profilecard/ProfileCardScreen.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt index b4b51dafd..7df40a08a 100644 --- a/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt +++ b/feature/profilecard/src/commonMain/kotlin/io/github/droidkaigi/confsched/profilecard/ProfileCardScreen.kt @@ -840,10 +840,9 @@ internal fun CardScreen( text = stringResource(ProfileCardRes.string.edit), modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.labelLarge, - color = Color.Black + color = Color.Black, ) } - } } } From 176c2f985d438e2b5c2d0ee8ba09432a66666417 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Sat, 31 Aug 2024 05:29:04 +0900 Subject: [PATCH 07/39] :wrench: Since it is necessary to display the asset if it is available, the condition of the isAvailable variable has been modified. --- .../io/github/droidkaigi/confsched/model/TimetableAsset.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableAsset.kt b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableAsset.kt index 89c9a8d52..4a282a12d 100644 --- a/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableAsset.kt +++ b/core/model/src/commonMain/kotlin/io/github/droidkaigi/confsched/model/TimetableAsset.kt @@ -5,5 +5,5 @@ public data class TimetableAsset( val slideUrl: String?, ) { val isAvailable: Boolean - get() = !videoUrl.isNullOrBlank() && !slideUrl.isNullOrBlank() + get() = videoUrl.isNullOrBlank().not() || slideUrl.isNullOrBlank().not() } From 2df302cc17f525dbe01caaf9c69b1deb7b6cecd6 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Sat, 31 Aug 2024 05:29:44 +0900 Subject: [PATCH 08/39] :sparkles: I added a process to provide assets by condition. --- .../data/sessions/FakeSessionsApiClient.kt | 75 +++++++++++++++++++ .../confsched/testing/robot/MiniRobots.kt | 16 +++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/sessions/FakeSessionsApiClient.kt b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/sessions/FakeSessionsApiClient.kt index a611d9c5c..78dec7cce 100644 --- a/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/sessions/FakeSessionsApiClient.kt +++ b/core/data/src/commonMain/kotlin/io/github/droidkaigi/confsched/data/sessions/FakeSessionsApiClient.kt @@ -30,6 +30,24 @@ public class FakeSessionsApiClient : SessionsApiClient { } } + public data object OperationalBothAssetAvailable : Status() { + override suspend fun sessionsAllResponse(): SessionsAllResponse { + return SessionsAllResponse.bothAssetAvailableFake() + } + } + + public data object OperationalOnlySlideAssetAvailable : Status() { + override suspend fun sessionsAllResponse(): SessionsAllResponse { + return SessionsAllResponse.onlySlideAssetAvailableFake() + } + } + + public data object OperationalOnlyVideoAssetAvailable : Status() { + override suspend fun sessionsAllResponse(): SessionsAllResponse { + return SessionsAllResponse.onlyVideoAssetAvailableFake() + } + } + public data object Error : Status() { override suspend fun sessionsAllResponse(): SessionsAllResponse { throw IOException("Fake IO Exception") @@ -216,6 +234,63 @@ public fun SessionsAllResponse.Companion.fake(): SessionsAllResponse { ) } +public fun SessionsAllResponse.Companion.bothAssetAvailableFake(): SessionsAllResponse { + val baseFake = fake() + val sessions = baseFake.sessions.map { session -> + session.copy( + asset = SessionAssetResponse( + videoUrl = "https://2024.droidkaigi.jp/", + slideUrl = "https://2024.droidkaigi.jp/", + ) + ) + } + + return SessionsAllResponse( + sessions = sessions, + rooms = baseFake.rooms, + speakers = baseFake.speakers, + categories = baseFake.categories, + ) +} + +public fun SessionsAllResponse.Companion.onlySlideAssetAvailableFake(): SessionsAllResponse { + val baseFake = fake() + val sessions = baseFake.sessions.map { session -> + session.copy( + asset = SessionAssetResponse( + videoUrl = null, + slideUrl = "https://2024.droidkaigi.jp/", + ) + ) + } + + return SessionsAllResponse( + sessions = sessions, + rooms = baseFake.rooms, + speakers = baseFake.speakers, + categories = baseFake.categories, + ) +} + +public fun SessionsAllResponse.Companion.onlyVideoAssetAvailableFake(): SessionsAllResponse { + val baseFake = fake() + val sessions = baseFake.sessions.map { session -> + session.copy( + asset = SessionAssetResponse( + videoUrl = "https://2024.droidkaigi.jp/", + slideUrl = null, + ) + ) + } + + return SessionsAllResponse( + sessions = sessions, + rooms = baseFake.rooms, + speakers = baseFake.speakers, + categories = baseFake.categories, + ) +} + private fun Instant.toCustomIsoString(): String { val timezone = TimeZone.of("Asia/Tokyo") val zonedDateTime = this.toLocalDateTime(timezone) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt index 8d7e80a27..42dd9923f 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/MiniRobots.kt @@ -10,6 +10,7 @@ import io.github.droidkaigi.confsched.data.eventmap.EventMapApiClient import io.github.droidkaigi.confsched.data.eventmap.FakeEventMapApiClient import io.github.droidkaigi.confsched.data.profilecard.ProfileCardDataStore import io.github.droidkaigi.confsched.data.sessions.FakeSessionsApiClient +import io.github.droidkaigi.confsched.data.sessions.FakeSessionsApiClient.Status import io.github.droidkaigi.confsched.data.sessions.SessionsApiClient import io.github.droidkaigi.confsched.data.settings.SettingsDataStore import io.github.droidkaigi.confsched.data.sponsors.FakeSponsorsApiClient @@ -28,6 +29,11 @@ import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.Setti import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.SettingsStatus.UseDotGothic16FontFamily import io.github.droidkaigi.confsched.testing.robot.SettingsDataStoreRobot.SettingsStatus.UseSystemDefaultFont import io.github.droidkaigi.confsched.testing.robot.SponsorsServerRobot.ServerStatus +import io.github.droidkaigi.confsched.testing.robot.TimetableServerRobot.ServerStatus.Error +import io.github.droidkaigi.confsched.testing.robot.TimetableServerRobot.ServerStatus.Operational +import io.github.droidkaigi.confsched.testing.robot.TimetableServerRobot.ServerStatus.OperationalBothAssetAvailable +import io.github.droidkaigi.confsched.testing.robot.TimetableServerRobot.ServerStatus.OperationalOnlySlideAssetAvailable +import io.github.droidkaigi.confsched.testing.robot.TimetableServerRobot.ServerStatus.OperationalOnlyVideoAssetAvailable import io.github.droidkaigi.confsched.testing.rules.RobotTestRule import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.TestDispatcher @@ -168,6 +174,9 @@ class DefaultDeviceSetupRobot @Inject constructor() : DeviceSetupRobot { interface TimetableServerRobot { enum class ServerStatus { Operational, + OperationalBothAssetAvailable, + OperationalOnlySlideAssetAvailable, + OperationalOnlyVideoAssetAvailable, Error, } @@ -180,8 +189,11 @@ class DefaultTimetableServerRobot @Inject constructor(sessionsApiClient: Session override fun setupTimetableServer(serverStatus: TimetableServerRobot.ServerStatus) { fakeSessionsApiClient.setup( when (serverStatus) { - TimetableServerRobot.ServerStatus.Operational -> FakeSessionsApiClient.Status.Operational - TimetableServerRobot.ServerStatus.Error -> FakeSessionsApiClient.Status.Error + Operational -> Status.Operational + OperationalBothAssetAvailable -> Status.OperationalBothAssetAvailable + OperationalOnlySlideAssetAvailable -> Status.OperationalOnlySlideAssetAvailable + OperationalOnlyVideoAssetAvailable -> Status.OperationalOnlyVideoAssetAvailable + Error -> Status.Error }, ) } From 50a53bb36480b1a764042c81dd1decc9954b6d08 Mon Sep 17 00:00:00 2001 From: todayama_r <13657682+Corvus400@users.noreply.github.com> Date: Sat, 31 Aug 2024 05:30:47 +0900 Subject: [PATCH 09/39] :recycle: Added VRTs for different asset conditions. --- .../robot/TimetableItemDetailScreenRobot.kt | 61 +++++++++++++++++++ .../sessions/TimetableItemDetailScreenTest.kt | 48 +++++++++++++++ .../component/TimetableItemDetailContent.kt | 20 ++++-- 3 files changed, 124 insertions(+), 5 deletions(-) diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/TimetableItemDetailScreenRobot.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/TimetableItemDetailScreenRobot.kt index 2cd5b549b..69cb867ae 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/TimetableItemDetailScreenRobot.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/robot/TimetableItemDetailScreenRobot.kt @@ -1,7 +1,9 @@ package io.github.droidkaigi.confsched.testing.robot import androidx.compose.ui.test.assertContentDescriptionEquals +import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertTextContains import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.hasTestTag @@ -11,6 +13,7 @@ import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToIndex +import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeUp import com.github.takahirom.roborazzi.Dump @@ -24,6 +27,9 @@ import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenLazyColu import io.github.droidkaigi.confsched.sessions.component.DescriptionMoreButtonTestTag import io.github.droidkaigi.confsched.sessions.component.SummaryCardTextTag import io.github.droidkaigi.confsched.sessions.component.TargetAudienceSectionTestTag +import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailContentArchiveSectionSlideButtonTestTag +import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailContentArchiveSectionTestTag +import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailContentArchiveSectionVideoButtonTestTag import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailHeadlineTestTag import io.github.droidkaigi.confsched.sessions.navigation.TimetableItemDetailDestination import javax.inject.Inject @@ -95,6 +101,12 @@ class TimetableItemDetailScreenRobot @Inject constructor( } } + fun scrollToAssetSection() { + composeTestRule + .onNode(hasTestTag(TimetableItemDetailScreenLazyColumnTestTag)) + .performScrollToNode(hasTestTag(TimetableItemDetailContentArchiveSectionTestTag)) + } + fun checkScreenCapture() { composeTestRule .onNode(isRoot()) @@ -169,6 +181,55 @@ class TimetableItemDetailScreenRobot @Inject constructor( .assertIsDisplayed() } + fun checkBothAssetButtonDisplayed() { + checkSlideAssetButtonDisplayed() + checkVideoAssetButtonDisplayed() + } + + fun checkOnlySlideAssetButtonDisplayed() { + checkSlideAssetButtonDisplayed() + checkVideoAssetButtonDoesNotDisplayed() + } + + fun checkOnlyVideoAssetButtonDisplayed() { + checkSlideAssetButtonDoesNotDisplayed() + checkVideoAssetButtonDisplayed() + } + + private fun checkSlideAssetButtonDisplayed() { + composeTestRule + .onAllNodes(hasTestTag(TimetableItemDetailContentArchiveSectionSlideButtonTestTag)) + .onFirst() + .assertExists() + .assertIsDisplayed() + .assertHasClickAction() + } + + private fun checkVideoAssetButtonDisplayed() { + composeTestRule + .onAllNodes(hasTestTag(TimetableItemDetailContentArchiveSectionVideoButtonTestTag)) + .onFirst() + .assertExists() + .assertIsDisplayed() + .assertHasClickAction() + } + + private fun checkSlideAssetButtonDoesNotDisplayed() { + composeTestRule + .onAllNodes(hasTestTag(TimetableItemDetailContentArchiveSectionSlideButtonTestTag)) + .onFirst() + .assertIsNotDisplayed() + .assertDoesNotExist() + } + + private fun checkVideoAssetButtonDoesNotDisplayed() { + composeTestRule + .onAllNodes(hasTestTag(TimetableItemDetailContentArchiveSectionVideoButtonTestTag)) + .onFirst() + .assertIsNotDisplayed() + .assertDoesNotExist() + } + companion object { val defaultSessionId = FakeSessionsApiClient.defaultSession.id } diff --git a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt index 24ed5c31c..c4e2ef110 100644 --- a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt +++ b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt @@ -156,6 +156,54 @@ class TimetableItemDetailScreenTest(private val testCase: DescribedBehavior