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

✨ [TimetableItemDetailScreen] I added a UI to display a message in the event of cancellation. #1026

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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.github.droidkaigi.confsched.data.sessions.response.CategoryResponse
import io.github.droidkaigi.confsched.data.sessions.response.LocaledResponse
import io.github.droidkaigi.confsched.data.sessions.response.RoomResponse
import io.github.droidkaigi.confsched.data.sessions.response.SessionAssetResponse
import io.github.droidkaigi.confsched.data.sessions.response.SessionMessageResponse
import io.github.droidkaigi.confsched.data.sessions.response.SessionResponse
import io.github.droidkaigi.confsched.data.sessions.response.SessionsAllResponse
import io.github.droidkaigi.confsched.data.sessions.response.SpeakerResponse
Expand Down Expand Up @@ -48,6 +49,12 @@ public class FakeSessionsApiClient : SessionsApiClient {
}
}

public data object OperationalMessageExists : Status() {
override suspend fun sessionsAllResponse(): SessionsAllResponse {
return SessionsAllResponse.messageExistsFake()
}
}

public data object Error : Status() {
override suspend fun sessionsAllResponse(): SessionsAllResponse {
throw IOException("Fake IO Exception")
Expand Down Expand Up @@ -126,6 +133,17 @@ public fun SessionsAllResponse.Companion.onlyVideoAssetAvailableFake(): Sessions
),
)

public fun SessionsAllResponse.Companion.messageExistsFake(): SessionsAllResponse = SessionsAllResponse.fake(
sessions = SessionResponse.fakes(
message = SessionMessageResponse.fake(),
),
)

public fun SessionMessageResponse.Companion.fake(): SessionMessageResponse = SessionMessageResponse(
ja = "このセッションは中止になりました",
en = "This session has been canceled.",
)

private fun RoomResponse.Companion.fakes(): List<RoomResponse> = listOf(
RoomResponse(name = LocaledResponse(ja = "Hedgehog ja", en = "Hedgehog"), id = 1, sort = 1),
RoomResponse(
Expand Down Expand Up @@ -180,6 +198,7 @@ private fun SessionResponse.Companion.fakes(
rooms: List<RoomResponse> = RoomResponse.fakes(),
categories: List<CategoryResponse> = CategoryResponse.fakes(),
asset: SessionAssetResponse = SessionAssetResponse.fake(),
message: SessionMessageResponse? = null,
): List<SessionResponse> {
val sessions = mutableListOf<SessionResponse>()

Expand All @@ -204,7 +223,7 @@ private fun SessionResponse.Companion.fakes(
sessionCategoryItemId = 3,
interpretationTarget = false,
asset = asset,
message = null,
message = message,
sessionType = "WELCOME_TALK",
levels = listOf("UNSPECIFIED"),
),
Expand Down Expand Up @@ -266,7 +285,7 @@ private fun SessionResponse.Companion.fakes(
roomId = room.id,
sessionCategoryItemId = sessionCategoryItemId,
sessionType = "NORMAL",
message = null,
message = message,
isPlenumSession = false,
targetAudience = "For App developer アプリ開発者向け",
interpretationTarget = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import io.github.droidkaigi.confsched.testing.robot.TimetableItemCardRobot.Langu
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.OperationalMessageExists
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
Expand Down Expand Up @@ -333,6 +334,7 @@ interface TimetableServerRobot {
OperationalBothAssetAvailable,
OperationalOnlySlideAssetAvailable,
OperationalOnlyVideoAssetAvailable,
OperationalMessageExists,
Error,
}

Expand All @@ -349,6 +351,7 @@ class DefaultTimetableServerRobot @Inject constructor(sessionsApiClient: Session
OperationalBothAssetAvailable -> Status.OperationalBothAssetAvailable
OperationalOnlySlideAssetAvailable -> Status.OperationalOnlySlideAssetAvailable
OperationalOnlyVideoAssetAvailable -> Status.OperationalOnlyVideoAssetAvailable
OperationalMessageExists -> Status.OperationalMessageExists
Error -> Status.Error
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.github.takahirom.roborazzi.captureRoboImage
import io.github.droidkaigi.confsched.data.sessions.FakeSessionsApiClient
import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailBookmarkIconTestTag
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailMessageRowTestTag
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailMessageRowTextTestTag
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreen
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenLazyColumnTestTag
import io.github.droidkaigi.confsched.sessions.component.DescriptionMoreButtonTestTag
Expand Down Expand Up @@ -118,6 +120,18 @@ class TimetableItemDetailScreenRobot @Inject constructor(
.performScrollToNode(hasTestTag(TimetableItemDetailContentArchiveSectionTestTag))
}

fun scrollToMessageRow() {
composeTestRule
.onNode(hasTestTag(TimetableItemDetailScreenLazyColumnTestTag))
.performScrollToNode(hasTestTag(TimetableItemDetailMessageRowTestTag))

// FIXME Without this, you won't be able to scroll to the exact middle of the message section.
composeTestRule.onRoot().performTouchInput {
swipeUp(startY = centerY, endY = centerY - 175)
}
waitUntilIdle()
}

fun checkScreenCapture() {
composeTestRule
.onNode(isRoot())
Expand Down Expand Up @@ -248,6 +262,15 @@ class TimetableItemDetailScreenRobot @Inject constructor(
.assertDoesNotExist()
}

fun checkMessageDisplayed() {
composeTestRule
.onAllNodes(hasTestTag(TimetableItemDetailMessageRowTextTestTag))
.onFirst()
.assertExists()
.assertIsDisplayed()
.assertTextEquals("This session has been canceled.")
}

companion object {
val defaultSessionId = FakeSessionsApiClient.defaultSession.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,22 @@ class TimetableItemDetailScreenTest(private val testCase: DescribedBehavior<Time
}
}
}
describe("when server is operational exists message") {
doIt {
setupTimetableServer(ServerStatus.OperationalMessageExists)
}
describe("when launch") {
doIt {
setupScreenContent()
scrollToMessageRow()
}
itShould("display message") {
captureScreenWithChecks {
checkMessageDisplayed()
}
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,24 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
Expand All @@ -25,9 +34,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import conference_app_2024.feature.sessions.generated.resources.image
import io.github.droidkaigi.confsched.compose.EventFlow
import io.github.droidkaigi.confsched.compose.rememberEventFlow
import io.github.droidkaigi.confsched.designsystem.component.LoadingText
Expand All @@ -42,6 +54,7 @@ import io.github.droidkaigi.confsched.droidkaigiui.compositionlocal.LocalSnackba
import io.github.droidkaigi.confsched.model.Lang
import io.github.droidkaigi.confsched.model.TimetableItem
import io.github.droidkaigi.confsched.model.TimetableItem.Session
import io.github.droidkaigi.confsched.model.TimetableItem.Special
import io.github.droidkaigi.confsched.model.TimetableItemId
import io.github.droidkaigi.confsched.model.fake
import io.github.droidkaigi.confsched.sessions.TimetableItemDetailScreenUiState.Loaded
Expand All @@ -52,11 +65,14 @@ import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailHead
import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailSummaryCard
import io.github.droidkaigi.confsched.sessions.component.TimetableItemDetailTopAppBar
import io.github.droidkaigi.confsched.sessions.navigation.TimetableItemDetailDestination
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview

const val timetableItemDetailScreenRouteItemIdParameterName = "timetableItemId"
const val TimetableItemDetailBookmarkIconTestTag = "TimetableItemDetailBookmarkIconTestTag"
const val TimetableItemDetailScreenLazyColumnTestTag = "TimetableItemDetailScreenLazyColumnTestTag"
const val TimetableItemDetailMessageRowTestTag = "TimetableItemDetailMessageRowTestTag"
const val TimetableItemDetailMessageRowTextTestTag = "TimetableItemDetailMessageRowTextTestTag"

fun NavGraphBuilder.sessionScreens(
onNavigationIconClick: () -> Unit,
Expand Down Expand Up @@ -233,6 +249,41 @@ private fun TimetableItemDetailScreen(
)
}

when (uiState.timetableItem) {
is Session -> uiState.timetableItem.message
is Special -> uiState.timetableItem.message
}?.let {
item {
Row(
modifier = Modifier
.padding(
start = 8.dp,
top = 24.dp,
end = 8.dp,
bottom = 4.dp,
)
.height(IntrinsicSize.Min)
.testTag(TimetableItemDetailMessageRowTestTag),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Icon(
modifier = Modifier.fillMaxHeight(),
imageVector = Icons.Filled.Info,
contentDescription = stringResource(SessionsRes.string.image),
tint = MaterialTheme.colorScheme.error,
)
Text(
modifier = Modifier.testTag(
TimetableItemDetailMessageRowTextTestTag,
),
text = it.currentLangTitle,
fontSize = 16.sp,
color = MaterialTheme.colorScheme.error,
)
}
}
}

item {
TimetableItemDetailSummaryCard(
timetableItem = uiState.timetableItem,
Expand Down
Loading