Skip to content

Commit

Permalink
Merge pull request #1090 from Corvus400/feature/implement_stamps_scre…
Browse files Browse the repository at this point in the history
…en_dialog_2

✨ Apply stamps initial dialog
  • Loading branch information
takahirom authored Sep 8, 2023
2 parents ba2622f + 6faaa0a commit 5567e13
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class KaigiAppTest {
fun checkNavigateToAchievementsShot() {
kaigiAppRobot {
goToAchievements()
capture()
captureFirstRootScreen()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.droidkaigi.confsched2023.data.achievements

import androidx.datastore.core.DataStore
import androidx.datastore.core.IOException
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
Expand Down Expand Up @@ -44,6 +45,28 @@ class AchievementsDataStore(private val dataStore: DataStore<Preferences>) {
}
}

internal suspend fun saveInitialDialogDisplayState(
isInitialDialogDisplay: Boolean,
) {
dataStore.edit { preferences ->
preferences[KEY_ACHIEVEMENTS] = isInitialDialogDisplay.toString()
}
}

public fun isInitialDialogDisplayStateStream(): Flow<Boolean> {
return dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences: Preferences ->
preferences[KEY_ACHIEVEMENTS]?.toBoolean() ?: false
}
}

companion object {
private val KEY_ACHIEVEMENTS = stringPreferencesKey("KEY_ACHIEVEMENTS")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ class DefaultAchievementRepository(
achievementsDataStore.resetAchievements()
}

override fun getIsInitialDialogDisplayStateStream(): Flow<Boolean> {
return achievementsDataStore.isInitialDialogDisplayStateStream()
}

override suspend fun displayedInitialDialog() {
achievementsDataStore.saveInitialDialogDisplayState(true)
}

companion object {
const val IS_ACHIEVEMENTS_ENABLED_KEY = "is_achievements_enable"
const val ACHIEVEMENT_DETAIL_DESCRIPTION_KEY = "achievements_detail_description"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package io.github.droidkaigi.confsched2023.data

import de.jensklingenberg.ktorfit.Ktorfit
import io.github.droidkaigi.confsched2023.data.achievements.AchievementsDataStore
import io.github.droidkaigi.confsched2023.data.achievements.DefaultAchievementRepository
import io.github.droidkaigi.confsched2023.data.auth.AuthApi
import io.github.droidkaigi.confsched2023.data.auth.DefaultAuthApi
import io.github.droidkaigi.confsched2023.data.contributors.AchievementRepository
import io.github.droidkaigi.confsched2023.data.contributors.ContributorsApiClient
import io.github.droidkaigi.confsched2023.data.contributors.DefaultContributorsApiClient
import io.github.droidkaigi.confsched2023.data.contributors.DefaultContributorsRepository
Expand Down Expand Up @@ -119,7 +117,6 @@ public val dataModule: Module = module {
singleOf(::NetworkService)
singleOf(::DefaultSessionsRepository) bind SessionsRepository::class
singleOf(::DefaultContributorsRepository) bind ContributorsRepository::class
singleOf(::DefaultAchievementRepository) bind AchievementRepository::class
singleOf(::DefaultStaffRepository) bind StaffRepository::class
singleOf(::DefaultSponsorsRepository) bind SponsorsRepository::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import kotlinx.collections.immutable.PersistentSet
import kotlinx.coroutines.flow.Flow

interface AchievementRepository {

fun getAchievementEnabledStream(): Flow<Boolean>
fun getAchievementDetailDescriptionStream(): Flow<String>
fun getResetAchievementsEnabledStream(): Flow<Boolean>
fun getAchievementsStream(): Flow<PersistentSet<Achievement>>
fun getIsInitialDialogDisplayStateStream(): Flow<Boolean>
suspend fun saveAchievements(achievement: Achievement)
suspend fun resetAchievements()
suspend fun displayedInitialDialog()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.droidkaigi.confsched2023.testing.robot

import androidx.compose.ui.test.isRoot
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeUp
import com.github.takahirom.roborazzi.captureRoboImage
Expand Down Expand Up @@ -39,7 +39,8 @@ class AchievementsScreenRobot @Inject constructor(

fun scroll() {
composeTestRule
.onRoot()
.onAllNodes(isRoot())
.onFirst()
.performTouchInput {
swipeUp(
startY = visibleSize.height * 3F / 4,
Expand All @@ -50,7 +51,8 @@ class AchievementsScreenRobot @Inject constructor(

fun checkScreenCapture() {
composeTestRule
.onNode(isRoot())
.onAllNodes(isRoot())
.onFirst()
.captureRoboImage()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.droidkaigi.confsched2023.testing.robot
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.isRoot
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import com.github.takahirom.roborazzi.captureRoboImage
import io.github.droidkaigi.confsched2023.main.MainScreenTab
Expand Down Expand Up @@ -38,6 +39,15 @@ class KaigiAppRobot @Inject constructor(
.captureRoboImage()
}

// FIXME I am doing this because capture() does not take a screenshot of the Achievement screen.
// If you know how to fix it, please fix it.
fun captureFirstRootScreen() {
composeTestRule
.onAllNodes(isRoot())
.onFirst()
.captureRoboImage()
}

fun goToAbout() {
composeTestRule
.onNode(hasTestTag(MainScreenTab.About.testTag))
Expand All @@ -54,7 +64,11 @@ class KaigiAppRobot @Inject constructor(

fun goToAchievements() {
composeTestRule
.onNode(hasTestTag(MainScreenTab.Achievements.testTag))
.onAllNodes(
matcher = hasTestTag(MainScreenTab.Achievements.testTag),
useUnmergedTree = true,
)
.onFirst()
.performClick()
waitUntilIdle()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package io.github.droidkaigi.confsched2023.achievements

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
Expand Down Expand Up @@ -68,11 +77,13 @@ fun AchievementsScreen(
snackbarHostState = snackbarHostState,
contentPadding = contentPadding,
onReset = viewModel::onReset,
onDisplayedInitialDialog = viewModel::onDisplayedInitialDialog,
)
}

data class AchievementsScreenUiState(
val achievementListUiState: AchievementListUiState,
val isShowInitialDialog: Boolean,
)

@Composable
Expand All @@ -81,6 +92,7 @@ private fun AchievementsScreen(
snackbarHostState: SnackbarHostState,
contentPadding: PaddingValues,
onReset: () -> Unit,
onDisplayedInitialDialog: () -> Unit,
) {
val layoutDirection = LocalLayoutDirection.current
Scaffold(
Expand All @@ -93,6 +105,11 @@ private fun AchievementsScreen(
bottom = contentPadding.calculateBottomPadding(),
),
content = { innerPadding ->
if (uiState.isShowInitialDialog) {
AchievementScreenDialog(
onDismissRequest = onDisplayedInitialDialog,
)
}
AchievementList(
uiState = uiState.achievementListUiState,
contentPadding = innerPadding,
Expand All @@ -106,3 +123,83 @@ private fun AchievementsScreen(
},
)
}

@Composable
fun AchievementScreenDialog(
onDismissRequest: () -> Unit,
) {
AlertDialog(
title = {
Text(
text = AchievementsStrings.DialogTitle.asString(),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
)
},
text = {
Column {
OrderedListText(
order = 1,
text = AchievementsStrings.DialogDescription1.asString(),
)
OrderedListText(
order = 2,
text = AchievementsStrings.DialogDescription2.asString(),
)
OrderedListText(
order = 3,
text = AchievementsStrings.DialogDescription3.asString(),
)
}
},
onDismissRequest = onDismissRequest,
confirmButton = {
Text(
modifier = Modifier
.clickable { onDismissRequest() },
text = AchievementsStrings.DialogConfirmButton.asString(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
)
},
)
}

@Composable
fun OrderedListText(
order: Int,
text: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
) {
Text(
text = "$order.",
modifier = Modifier
.width(16.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

// FIXME The dialog preview test does not pass.
//  If you know how to fix it, I would appreciate it if you could respond.
// @MultiLanguagePreviews
// @Composable
// fun StampsScreenDialogPreview() {
// KaigiTheme {
// Surface {
// StampsScreenDialog(
// onDismissRequest = {},
// )
// }
// }
// }
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ class AchievementsScreenViewModel @Inject constructor(
initialValue = persistentSetOf(),
)

private val isInitialDialogDisplayFlow: StateFlow<Boolean?> =
achievementRepository.getIsInitialDialogDisplayStateStream()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null,
)

private val resetAchievementsEnabledStateFlow: StateFlow<Boolean> =
achievementRepository.getResetAchievementsEnabledStream()
.handleErrorAndRetry(
Expand Down Expand Up @@ -114,9 +122,11 @@ class AchievementsScreenViewModel @Inject constructor(

val uiState = buildUiState(
achievementAnimationListState,
) { achievementListUiState ->
isInitialDialogDisplayFlow,
) { achievementListUiState, isDisplayedInitialDialog ->
AchievementsScreenUiState(
achievementListUiState = achievementListUiState,
isShowInitialDialog = isDisplayedInitialDialog?.not() ?: false,
)
}

Expand All @@ -125,4 +135,10 @@ class AchievementsScreenViewModel @Inject constructor(
achievementRepository.resetAchievements()
}
}

fun onDisplayedInitialDialog() {
viewModelScope.launch {
achievementRepository.displayedInitialDialog()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,37 @@ sealed class AchievementsStrings : Strings<AchievementsStrings>(Bindings) {

data object DescriptionNotes : AchievementsStrings()

data object DialogTitle : AchievementsStrings()

data object DialogConfirmButton : AchievementsStrings()

data object DialogDescription1 : AchievementsStrings()

data object DialogDescription2 : AchievementsStrings()

data object DialogDescription3 : AchievementsStrings()

private object Bindings : StringsBindings<AchievementsStrings>(
Lang.Japanese to { item, _ ->
when (item) {
Title -> "Achievements"
DescriptionNotes -> "※ この企画は変更または中止になる可能性があります"
DialogTitle -> "アチーブメントに関する\n注意事項"
DialogConfirmButton -> "OK"
DialogDescription1 -> "アプリを削除してしまうと、データは復元できません。"
DialogDescription2 -> "機種やバージョンによって、お手持ちの端末でうまく動作しない場合があるかもしれません。ご理解いただけますと幸いです。"
DialogDescription3 -> "この企画は変更される可能性があります。"
}
},
Lang.English to { item, _ ->
Lang.English to { item, bindings ->
when (item) {
Title -> "Achievements"
DescriptionNotes -> "※ This program is subject to change or cancellation."
DialogTitle -> "Notes on Achievements"
DialogConfirmButton -> bindings.defaultBinding(item, bindings)
DialogDescription1 -> "If you delete the application, data cannot be recovered."
DialogDescription2 -> "Depending on the model and version, the application may not work well on your device. We appreciate your understanding."
DialogDescription3 -> "This project is subject to change."
}
},
default = Lang.Japanese,
Expand Down

0 comments on commit 5567e13

Please sign in to comment.