diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenter.kt index d61a6dedf9..7fc97d1817 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenter.kt @@ -32,8 +32,8 @@ import org.cru.godtools.db.repository.ToolsRepository import org.cru.godtools.model.Tool import org.cru.godtools.ui.dashboard.home.AllFavoritesPresenter.UiState import org.cru.godtools.ui.tooldetails.ToolDetailsScreen -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus @@ -48,7 +48,7 @@ class AllFavoritesPresenter @AssistedInject constructor( ) : Presenter { @ConsistentCopyVisibility data class UiState internal constructor( - val tools: List = emptyList(), + val tools: List = emptyList(), internal val eventSink: (UiEvent) -> Unit = {}, ) : CircuitUiState @@ -66,11 +66,11 @@ class AllFavoritesPresenter @AssistedInject constructor( tools = tools.mapNotNull { tool -> val toolCode = tool.code ?: return@mapNotNull null key(toolCode) { - lateinit var state: ToolCard.State + lateinit var state: ToolCardPresenter.UiState state = toolCardPresenter.present(tool) { when (it) { - ToolCard.Event.Click, - ToolCard.Event.OpenTool -> { + ToolCardEvent.Click, + ToolCardEvent.OpenTool -> { val intent = context.createToolIntent( tool = tool, languages = listOfNotNull( @@ -88,15 +88,12 @@ class AllFavoritesPresenter @AssistedInject constructor( } } - ToolCard.Event.OpenToolDetails -> { + ToolCardEvent.OpenToolDetails -> { eventBus.post( OpenAnalyticsActionEvent(ACTION_OPEN_TOOL_DETAILS, toolCode, SOURCE_FAVORITE) ) navigator.goTo(ToolDetailsScreen(toolCode)) } - - ToolCard.Event.PinTool, - ToolCard.Event.UnpinTool -> error("$it should be handled by the ToolCardPresenter") } } state diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenter.kt index eb7b54e946..b822a4d103 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenter.kt @@ -35,8 +35,8 @@ import org.cru.godtools.ui.banner.BannerPresenter import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerPresenter import org.cru.godtools.ui.dashboard.home.HomePresenter.UiState import org.cru.godtools.ui.tooldetails.ToolDetailsScreen -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus @@ -54,8 +54,8 @@ class HomePresenter @AssistedInject constructor( data class UiState( val dataLoaded: Boolean = true, val banner: Banner.UiState? = null, - val spotlightLessons: List = emptyList(), - val favoriteTools: List = emptyList(), + val spotlightLessons: List = emptyList(), + val favoriteTools: List = emptyList(), val eventSink: (UiEvent) -> Unit = {}, ) : CircuitUiState @@ -91,10 +91,10 @@ class HomePresenter @AssistedInject constructor( val lessonCode = lesson.code ?: return@mapNotNull null key(lessonCode) { - lateinit var lessonState: ToolCard.State + lateinit var lessonState: ToolCardPresenter.UiState lessonState = toolCardPresenter.present(lesson) { when (it) { - ToolCard.Event.Click -> { + ToolCardEvent.Click -> { val intent = context.createToolIntent( tool = lesson, languages = listOfNotNull(lessonState.translation?.languageCode), @@ -130,11 +130,11 @@ class HomePresenter @AssistedInject constructor( val toolCode = tool.code ?: return@mapNotNull null key(toolCode) { - lateinit var state: ToolCard.State + lateinit var state: ToolCardPresenter.UiState state = toolCardPresenter.present(tool) { when (it) { - ToolCard.Event.Click, - ToolCard.Event.OpenTool -> { + ToolCardEvent.Click, + ToolCardEvent.OpenTool -> { val intent = context.createToolIntent( tool = tool, languages = listOfNotNull( @@ -152,15 +152,12 @@ class HomePresenter @AssistedInject constructor( } } - ToolCard.Event.OpenToolDetails -> { + ToolCardEvent.OpenToolDetails -> { eventBus.post( OpenAnalyticsActionEvent(ACTION_OPEN_TOOL_DETAILS, toolCode, SOURCE_FAVORITE) ) navigator.goTo(ToolDetailsScreen(toolCode)) } - - ToolCard.Event.PinTool, - ToolCard.Event.UnpinTool -> error("$it should be handled by the ToolCardPresenter") } } state diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenter.kt index d2b32ab6a0..cb093fc820 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenter.kt @@ -45,8 +45,8 @@ import org.cru.godtools.model.Language import org.cru.godtools.model.Language.Companion.filterByDisplayAndNativeName import org.cru.godtools.ui.dashboard.filters.FilterMenu import org.cru.godtools.ui.dashboard.lessons.LessonsPresenter.UiState -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus @@ -65,7 +65,7 @@ class LessonsPresenter @AssistedInject constructor( // region UiState data class UiState( val languageFilter: FilterMenu.UiState = FilterMenu.UiState(), - val lessons: List = emptyList(), + val lessons: List = emptyList(), ) : CircuitUiState // endregion UiState @@ -140,7 +140,7 @@ class LessonsPresenter @AssistedInject constructor( } @Composable - private fun rememberLessons(locale: Locale): List { + private fun rememberLessons(locale: Locale): List { val lessons by remember(locale) { toolsRepository.getLessonsFlowByLanguage(locale) .map { it.filterNot { it.isHidden }.sortedBy { it.defaultOrder } } @@ -148,13 +148,13 @@ class LessonsPresenter @AssistedInject constructor( return lessons.map { tool -> key(tool.code) { - lateinit var toolState: ToolCard.State + lateinit var toolState: ToolCardPresenter.UiState toolState = toolCardPresenter.present( tool = tool, customLocale = locale, eventSink = { when (it) { - ToolCard.Event.Click -> { + ToolCardEvent.Click, ToolCardEvent.OpenTool -> { eventBus.post(OpenAnalyticsActionEvent(ACTION_OPEN_LESSON, tool.code, SOURCE_LESSONS)) navigator.goTo( IntentScreen( @@ -167,10 +167,7 @@ class LessonsPresenter @AssistedInject constructor( ) } - ToolCard.Event.OpenTool, - ToolCard.Event.OpenToolDetails, - ToolCard.Event.PinTool, - ToolCard.Event.UnpinTool -> Unit + ToolCardEvent.OpenToolDetails -> Unit } } ) diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsLayout.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsLayout.kt index 2f694017b4..1fa39facec 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsLayout.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsLayout.kt @@ -34,6 +34,7 @@ import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiEvent import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState import org.cru.godtools.ui.tools.SquareToolCard import org.cru.godtools.ui.tools.ToolCard +import org.cru.godtools.ui.tools.ToolCardPresenter internal val MARGIN_TOOLS_LAYOUT_HORIZONTAL = 16.dp @@ -163,7 +164,7 @@ private fun ToolsHeader(mode: UiState.Mode, modifier: Modifier = Modifier) = Col } @Composable -private fun ToolSpotlight(tools: List, modifier: Modifier = Modifier) { +private fun ToolSpotlight(tools: List, modifier: Modifier = Modifier) { Column(modifier = modifier.fillMaxWidth()) { Text( stringResource(R.string.dashboard_tools_section_spotlight_label), diff --git a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt index b80f852a3d..ccf4666882 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenter.kt @@ -44,8 +44,8 @@ import org.cru.godtools.ui.dashboard.tools.ToolFiltersStateProducer.Filters import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState.Mode import org.cru.godtools.ui.tooldetails.ToolDetailsScreen -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.greenrobot.eventbus.EventBus class ToolsPresenter @AssistedInject internal constructor( @@ -66,9 +66,9 @@ class ToolsPresenter @AssistedInject internal constructor( val mode: Mode = Mode.ALL_TOOLS, val banner: Banner.UiState? = null, val dataLoaded: Boolean = true, - val spotlightTools: List = emptyList(), + val spotlightTools: List = emptyList(), val filters: Filters = Filters(), - val tools: List = emptyList(), + val tools: List = emptyList(), // TODO: temporary until personalization is rolled out to everyone, // then this can be removed and the mode logic simplified val isPersonalizationEnabled: Boolean = false, @@ -151,7 +151,7 @@ class ToolsPresenter @AssistedInject internal constructor( private fun rememberSpotlightTools( secondLanguage: Language?, eventSink: (UiEvent) -> Unit, - ): List? { + ): List? { val tools by remember { toolsRepository.getNormalToolsFlow() .map { it.filter { !it.isHidden && it.isSpotlight }.sortedWith(Tool.COMPARATOR_DEFAULT_ORDER) } @@ -166,13 +166,10 @@ class ToolsPresenter @AssistedInject internal constructor( secondLanguage = secondLanguage, eventSink = { when (it) { - ToolCard.Event.Click, - ToolCard.Event.OpenTool, - ToolCard.Event.OpenToolDetails -> + ToolCardEvent.Click, + ToolCardEvent.OpenTool, + ToolCardEvent.OpenToolDetails -> toolCode?.let { eventSink(UiEvent.OpenToolDetails(it, SOURCE_SPOTLIGHT)) } - - ToolCard.Event.PinTool, - ToolCard.Event.UnpinTool -> error("$it should be handled by the ToolCardPresenter") } } ) @@ -185,7 +182,7 @@ class ToolsPresenter @AssistedInject internal constructor( category: String?, language: Language?, eventSink: (UiEvent) -> Unit, - ): List? { + ): List? { val locale = language?.code val tools by remember(mode, category, locale) { filteredToolsFlowProducer.getFlow(mode, category, locale) } .collectAsState(null) @@ -199,13 +196,10 @@ class ToolsPresenter @AssistedInject internal constructor( secondLanguage = language, eventSink = { when (it) { - ToolCard.Event.Click, - ToolCard.Event.OpenTool, - ToolCard.Event.OpenToolDetails -> + ToolCardEvent.Click, + ToolCardEvent.OpenTool, + ToolCardEvent.OpenToolDetails -> toolCode?.let { eventSink(UiEvent.OpenToolDetails(it, SOURCE_ALL_TOOLS)) } - - ToolCard.Event.PinTool, - ToolCard.Event.UnpinTool -> error("$it should be handled by the ToolCardPresenter") } } ) diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayout.kt b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayout.kt index 7f7f80e32a..1578456586 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayout.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayout.kt @@ -75,6 +75,7 @@ import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.UiState import org.cru.godtools.ui.tooldetails.analytics.model.ToolDetailsScreenEvent import org.cru.godtools.ui.tools.AvailableInLanguage import org.cru.godtools.ui.tools.DownloadProgressIndicator +import org.cru.godtools.ui.tools.ToolCardPresenter import org.cru.godtools.ui.tools.VariantToolCard private val TOOL_DETAILS_HORIZONTAL_MARGIN = 32.dp diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenter.kt index 8e7fa0baed..43b7f57cb9 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenter.kt @@ -71,8 +71,8 @@ import org.cru.godtools.ui.drawer.DrawerMenuPresenter import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.Page import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.UiEvent import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.UiState -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus @@ -221,7 +221,7 @@ class ToolDetailsPresenter @AssistedInject constructor( metaToolCode: String?, secondLanguage: Language?, onVariantSelect: (String) -> Unit, - ): ImmutableList { + ): List { if (metaToolCode == null) return persistentListOf() val onVariantSelect by rememberUpdatedState(onVariantSelect) @@ -237,14 +237,13 @@ class ToolDetailsPresenter @AssistedInject constructor( loadAvailableLanguages = true, eventSink = { when (it) { - ToolCard.Event.Click -> tool.code?.let { onVariantSelect(it) } + ToolCardEvent.Click -> tool.code?.let { onVariantSelect(it) } else -> Unit } } ) } } - .toImmutableList() } @Composable diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsScreen.kt b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsScreen.kt index 5d922c1a6f..ff862e27a4 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsScreen.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsScreen.kt @@ -15,7 +15,7 @@ import org.cru.godtools.model.Language import org.cru.godtools.model.Tool import org.cru.godtools.model.Translation import org.cru.godtools.ui.drawer.DrawerMenuScreen -import org.cru.godtools.ui.tools.ToolCard +import org.cru.godtools.ui.tools.ToolCardPresenter @Parcelize data class ToolDetailsScreen(val initialTool: String, val secondLanguage: Locale? = null) : Screen { @@ -32,7 +32,7 @@ data class ToolDetailsScreen(val initialTool: String, val secondLanguage: Locale val secondLanguage: Language? = null, val pages: ImmutableList = persistentListOf(Page.DESCRIPTION), val availableLanguages: ImmutableList = persistentListOf(), - val variants: ImmutableList = persistentListOf(), + val variants: List = listOf(), val drawerState: DrawerMenuScreen.State = DrawerMenuScreen.State(), val eventSink: (UiEvent) -> Unit = {}, ) : CircuitUiState diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenter.kt new file mode 100644 index 0000000000..4af095080d --- /dev/null +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenter.kt @@ -0,0 +1,181 @@ +package org.cru.godtools.ui.tools + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import java.util.Locale +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.ccci.gto.android.common.compose.util.rememberStateFlow +import org.cru.godtools.base.Settings +import org.cru.godtools.base.ToolFileSystem +import org.cru.godtools.db.repository.AttachmentsRepository +import org.cru.godtools.db.repository.LanguagesRepository +import org.cru.godtools.db.repository.ToolsRepository +import org.cru.godtools.db.repository.TranslationsRepository +import org.cru.godtools.db.repository.UserCountersRepository +import org.cru.godtools.db.repository.produceLatestTranslationState +import org.cru.godtools.db.repository.rememberAttachmentFile +import org.cru.godtools.db.repository.rememberLanguage +import org.cru.godtools.model.Language +import org.cru.godtools.model.Tool +import org.cru.godtools.shared.user.activity.UserCounterNames.LESSON_COMPLETION +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState + +@Singleton +internal class DefaultToolCardPresenter @Inject constructor( + private val fileSystem: ToolFileSystem, + private val settings: Settings, + private val attachmentsRepository: AttachmentsRepository, + private val languagesRepository: LanguagesRepository, + private val toolsRepository: ToolsRepository, + private val translationsRepository: TranslationsRepository, + private val userCountersRepository: UserCountersRepository, +) : ToolCardPresenter { + @Composable + @OptIn(ExperimentalCoroutinesApi::class) + override fun present( + tool: Tool, + customLocale: Locale?, + loadAppLanguage: Boolean, + secondLanguage: Language?, + loadAvailableLanguages: Boolean, + eventSink: (ToolCardEvent) -> Unit, + ): UiState { + val coroutineScope = rememberCoroutineScope() + val toolCode by rememberUpdatedState(tool.code) + + // App Translation + val appLocale by settings.produceAppLocaleState() + val appTranslationFlow = remember { + combine( + snapshotFlow { toolCode }, + snapshotFlow { appLocale }, + ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } + .flatMapLatest { it } + .shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1) + } + val appTranslation by appTranslationFlow.collectAsState(null) + val appLanguage = if (loadAppLanguage) languagesRepository.rememberLanguage(appLocale) else null + + // Custom Translation + val customLocaleFlow = rememberStateFlow(customLocale) + val customLanguage = languagesRepository.rememberLanguage(customLocale) + val customTranslationFlow = remember(customLocaleFlow) { + combine( + snapshotFlow { toolCode }, + customLocaleFlow + ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } + .flatMapLatest { it } + .shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1) + } + val customTranslation by customTranslationFlow.collectAsState(null) + + // Default Translation + val defaultLocale by rememberUpdatedState(tool.defaultLocale) + val defaultTranslationFlow = remember { + combine( + snapshotFlow { toolCode }, + snapshotFlow { defaultLocale } + ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } + .flatMapLatest { it } + .onStart { emit(null) } + } + + // Primary Translation + var isLoaded by remember { mutableStateOf(false) } + val translation by remember { + combine( + customLocaleFlow.flatMapLatest { if (it != null) customTranslationFlow else appTranslationFlow }, + defaultTranslationFlow + ) { t1, t2 -> t1 ?: t2 } + .onEach { isLoaded = true } + }.collectAsState(null) + + // Second Translation + val secondTranslation by translationsRepository.produceLatestTranslationState(toolCode, secondLanguage?.code) + val secondLanguageAvailable by remember { derivedStateOf { secondTranslation != null } } + + return UiState( + toolCode = toolCode, + tool = tool, + isLoaded = isLoaded, + banner = attachmentsRepository.rememberAttachmentFile(fileSystem, tool.bannerId), + language = when (customLocale) { + null -> appLanguage + else -> customLanguage + }, + languageAvailable = when (customLocale) { + null -> appTranslation != null + else -> customTranslation != null + }, + translation = translation, + appLanguage = appLanguage, + appLanguageAvailable = appTranslation != null, + secondLanguage = secondLanguage, + secondLanguageAvailable = secondLanguageAvailable, + progress = rememberProgress(toolCode, tool.progress), + availableLanguages = when { + !loadAvailableLanguages -> 0 + else -> toolCode?.let { rememberAvailableLanguages(it) } ?: 0 + }, + ) { + when (it) { + UiEvent.PinTool -> coroutineScope.launch { + withContext(NonCancellable) { toolCode?.let { toolsRepository.pinTool(it) } } + } + + UiEvent.UnpinTool -> coroutineScope.launch { + withContext(NonCancellable) { toolCode?.let { toolsRepository.unpinTool(it) } } + } + + is ToolCardEvent -> eventSink(it) + } + } + } + + @Composable + private fun rememberProgress(toolCode: String?, progress: Double?): UiState.Progress? { + val completed by remember(toolCode) { + when { + toolCode == null -> flowOf(false) + else -> userCountersRepository.findCounterFlow(LESSON_COMPLETION(toolCode)).map { (it?.count ?: 0) > 0 } + } + }.collectAsState(false) + + return when { + completed -> UiState.Progress.Completed + progress != null -> UiState.Progress.InProgress(progress) + else -> null + } + } + + @Composable + private fun rememberAvailableLanguages(tool: String) = remember(tool) { + translationsRepository.getTranslationsFlowForTool(tool) + .map { it.distinctBy { it.languageCode }.count() } + .distinctUntilChanged() + }.collectAsState(0).value +} diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/FavoriteAction.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/FavoriteAction.kt index aa3beffaf2..db24d16a04 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/FavoriteAction.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/FavoriteAction.kt @@ -24,11 +24,13 @@ import androidx.compose.ui.unit.dp import org.ccci.gto.android.common.compose.foundation.layout.padding import org.cru.godtools.R import org.cru.godtools.model.getName +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState internal const val TEST_TAG_FAVORITE_ACTION = "favorite_action" @Composable -internal fun FavoriteAction(state: ToolCard.State, modifier: Modifier = Modifier, confirmRemoval: Boolean = true) { +internal fun FavoriteAction(state: UiState, modifier: Modifier = Modifier, confirmRemoval: Boolean = true) { val tool by rememberUpdatedState(state.tool) val isFavorite by remember { derivedStateOf { tool?.isFavorite == true } } val eventSink by rememberUpdatedState(state.eventSink) @@ -38,9 +40,9 @@ internal fun FavoriteAction(state: ToolCard.State, modifier: Modifier = Modifier Surface( onClick = { when { - !isFavorite -> eventSink(ToolCard.Event.PinTool) + !isFavorite -> eventSink(UiEvent.PinTool) confirmRemoval -> showRemovalConfirmation = true - else -> eventSink(ToolCard.Event.UnpinTool) + else -> eventSink(UiEvent.UnpinTool) } }, shape = CircleShape, @@ -75,7 +77,7 @@ internal fun FavoriteAction(state: ToolCard.State, modifier: Modifier = Modifier confirmButton = { TextButton( onClick = { - eventSink(ToolCard.Event.UnpinTool) + eventSink(UiEvent.UnpinTool) showRemovalConfirmation = false } ) { Text(stringResource(R.string.tools_list_remove_favorite_dialog_confirm)) } diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/LessonToolCard.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/LessonToolCard.kt index 5234d57421..5a9a1d89ce 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/LessonToolCard.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/LessonToolCard.kt @@ -22,11 +22,13 @@ import kotlin.math.roundToInt import org.ccci.gto.android.common.compose.ui.draw.invisibleIf import org.cru.godtools.R import org.cru.godtools.base.ui.util.ProvideLayoutDirectionFromLocale -import org.cru.godtools.ui.tools.ToolCard.State.Progress +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState.Progress @Composable fun LessonToolCard( - state: ToolCard.State, + state: UiState, modifier: Modifier = Modifier, showLanguage: Boolean = false, showProgress: Boolean = false, @@ -37,7 +39,7 @@ fun LessonToolCard( val eventSink by rememberUpdatedState(state.eventSink) ElevatedCard( - onClick = { eventSink(ToolCard.Event.Click) }, + onClick = { eventSink(ToolCardEvent.Click) }, elevation = toolCardElevation, modifier = modifier.fillMaxWidth() ) { @@ -78,9 +80,7 @@ fun LessonToolCard( (state.progress.progress * 100).roundToInt().coerceIn(0, 100) ) - Progress.Completed -> stringResource( - R.string.dashboard_lessons_progress_completed - ) + Progress.Completed -> stringResource(R.string.dashboard_lessons_progress_completed) }, color = MaterialTheme.colorScheme.primary, ) diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/SquareToolCard.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/SquareToolCard.kt index 12ee8f971c..30ea7b8f7d 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/SquareToolCard.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/SquareToolCard.kt @@ -19,10 +19,13 @@ import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.unit.dp import org.ccci.gto.android.common.compose.ui.draw.invisibleIf import org.cru.godtools.base.ui.util.ProvideLayoutDirectionFromLocale +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState @Composable fun SquareToolCard( - state: ToolCard.State, + state: UiState, modifier: Modifier = Modifier, showCategory: Boolean = true, showSecondLanguage: Boolean = false, @@ -35,7 +38,7 @@ fun SquareToolCard( ProvideLayoutDirectionFromLocale(locale = state.translation?.languageCode) { ElevatedCard( - onClick = { eventSink(ToolCard.Event.Click) }, + onClick = { eventSink(ToolCardEvent.Click) }, elevation = toolCardElevation, modifier = modifier.width(189.dp) ) { @@ -78,7 +81,7 @@ fun SquareToolCard( Text( "", minLines = 2, - style = state.toolNameStyle, + style = toolNameStyle, modifier = Modifier.clearAndSetSemantics { hideFromAccessibility() } ) if (showCategory) { @@ -108,7 +111,7 @@ fun SquareToolCard( } @Composable -private fun SquareToolCardSecondLanguage(state: ToolCard.State) = ToolCardInfoContent { +private fun SquareToolCardSecondLanguage(state: UiState) = ToolCardInfoContent { val available by rememberUpdatedState(state.secondLanguageAvailable) AvailableInLanguage( diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCard.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCard.kt index 7350b5dfbe..f793617a5b 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCard.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCard.kt @@ -17,57 +17,14 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.slack.circuit.runtime.CircuitUiEvent -import com.slack.circuit.runtime.CircuitUiState -import java.io.File import org.ccci.gto.android.common.androidx.compose.foundation.layout.widthIn import org.cru.godtools.base.ui.util.ProvideLayoutDirectionFromLocale -import org.cru.godtools.downloadmanager.DownloadProgress -import org.cru.godtools.model.Language -import org.cru.godtools.model.Tool -import org.cru.godtools.model.Translation - -object ToolCard { - data class State( - val toolCode: String? = null, - val tool: Tool? = null, - val isLoaded: Boolean = true, - val banner: File? = null, - val language: Language? = null, - val languageAvailable: Boolean = false, - val translation: Translation? = null, - val appLanguage: Language? = null, - val appLanguageAvailable: Boolean = false, - val secondLanguage: Language? = null, - val secondLanguageAvailable: Boolean = false, - val progress: Progress? = null, - val availableLanguages: Int = 0, - val downloadProgress: DownloadProgress? = null, - val eventSink: (Event) -> Unit = {}, - ) : CircuitUiState { - sealed interface Progress { - val progress: Double - - @JvmInline - value class InProgress(override val progress: Double) : Progress - data object Completed : Progress { - override val progress = 1.0 - } - } - } - - sealed interface Event : CircuitUiEvent { - data object Click : Event - data object OpenTool : Event - data object OpenToolDetails : Event - data object PinTool : Event - data object UnpinTool : Event - } -} +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState @Composable fun ToolCard( - state: ToolCard.State, + state: UiState, modifier: Modifier = Modifier, confirmRemovalFromFavorites: Boolean = false, showActions: Boolean = true, @@ -79,7 +36,7 @@ fun ToolCard( ProvideLayoutDirectionFromLocale(locale = state.translation?.languageCode) { ElevatedCard( - onClick = { eventSink(ToolCard.Event.Click) }, + onClick = { eventSink(ToolCardEvent.Click) }, elevation = toolCardElevation, interactionSource = interactionSource, modifier = modifier diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardActions.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardActions.kt index b82184788e..307cfef041 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardActions.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardActions.kt @@ -12,28 +12,26 @@ import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import org.cru.godtools.R +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState @Composable internal fun ToolCardActions( - state: ToolCard.State, + state: UiState, modifier: Modifier = Modifier, buttonModifier: Modifier = Modifier, buttonWeightFill: Boolean = true, ) = Row(modifier = modifier) { - val eventSink by rememberUpdatedState(state.eventSink) - val buttonContentPadding = PaddingValues(horizontal = 4.dp, vertical = 8.dp) val buttonMinHeight = 30.dp CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides buttonMinHeight) { OutlinedButton( - onClick = { eventSink(ToolCard.Event.OpenToolDetails) }, + onClick = { state.eventSink(ToolCardEvent.OpenToolDetails) }, contentPadding = buttonContentPadding, modifier = buttonModifier .alignByBaseline() @@ -45,9 +43,11 @@ internal fun ToolCardActions( style = MaterialTheme.typography.labelMedium ) } + Spacer(Modifier.width(8.dp)) + Button( - onClick = { eventSink(ToolCard.Event.OpenTool) }, + onClick = { state.eventSink(ToolCardEvent.OpenTool) }, contentPadding = buttonContentPadding, modifier = buttonModifier .alignByBaseline() diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardComponents.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardComponents.kt index 0d163d9033..b46d1a1896 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardComponents.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardComponents.kt @@ -22,12 +22,13 @@ import coil.compose.AsyncImage import org.cru.godtools.base.ui.theme.GodToolsTheme import org.cru.godtools.base.ui.util.getCategory import org.cru.godtools.model.getName +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState internal const val TEST_TAG_TOOL_CATEGORY = "tool_category" internal val toolCardElevation @Composable get() = elevatedCardElevation(defaultElevation = 4.dp) -internal val ToolCard.State.toolNameStyle: TextStyle +internal val toolNameStyle: TextStyle @Composable get() { val baseStyle = MaterialTheme.typography.titleMedium @@ -42,7 +43,7 @@ private val toolCardInfoLabelColor: Color @Composable get() { private val toolCardInfoLabelStyle @Composable get() = MaterialTheme.typography.labelSmall @Composable -internal fun ToolBanner(state: ToolCard.State, modifier: Modifier = Modifier) = AsyncImage( +internal fun ToolBanner(state: UiState, modifier: Modifier = Modifier) = AsyncImage( model = state.banner, contentDescription = null, contentScale = ContentScale.Crop, @@ -51,16 +52,14 @@ internal fun ToolBanner(state: ToolCard.State, modifier: Modifier = Modifier) = @Composable internal fun ToolName( - state: ToolCard.State, + state: UiState, modifier: Modifier = Modifier, minLines: Int = 1, maxLines: Int = Int.MAX_VALUE, ) { - val style = state.toolNameStyle - Text( state.translation.getName(state.tool).orEmpty(), - style = style, + style = toolNameStyle, maxLines = maxLines, minLines = minLines, overflow = TextOverflow.Ellipsis, @@ -69,7 +68,7 @@ internal fun ToolName( } @Composable -internal fun ToolCategory(state: ToolCard.State, modifier: Modifier = Modifier) = Text( +internal fun ToolCategory(state: UiState, modifier: Modifier = Modifier) = Text( state.tool.getCategory(LocalContext.current), style = toolCategoryStyle, maxLines = 1, diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardModule.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardModule.kt new file mode 100644 index 0000000000..a3bcd3c488 --- /dev/null +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardModule.kt @@ -0,0 +1,13 @@ +package org.cru.godtools.ui.tools + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class ToolCardModule { + @Binds + abstract fun toolCardPresenter(presenter: DefaultToolCardPresenter): ToolCardPresenter +} diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter.kt index fa3b8622d1..1c484bf620 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter.kt @@ -1,183 +1,62 @@ package org.cru.godtools.ui.tools import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow +import com.slack.circuit.runtime.CircuitUiEvent +import com.slack.circuit.runtime.CircuitUiState +import java.io.File import java.util.Locale -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.launch -import org.ccci.gto.android.common.compose.util.rememberStateFlow -import org.cru.godtools.base.Settings -import org.cru.godtools.base.ToolFileSystem -import org.cru.godtools.db.repository.AttachmentsRepository -import org.cru.godtools.db.repository.LanguagesRepository -import org.cru.godtools.db.repository.ToolsRepository -import org.cru.godtools.db.repository.TranslationsRepository -import org.cru.godtools.db.repository.UserCountersRepository -import org.cru.godtools.db.repository.produceLatestTranslationState -import org.cru.godtools.db.repository.rememberAttachmentFile -import org.cru.godtools.db.repository.rememberLanguage +import org.cru.godtools.downloadmanager.DownloadProgress import org.cru.godtools.model.Language import org.cru.godtools.model.Tool -import org.cru.godtools.shared.user.activity.UserCounterNames.LESSON_COMPLETION +import org.cru.godtools.model.Translation -@Singleton -class ToolCardPresenter @Inject constructor( - private val fileSystem: ToolFileSystem, - private val settings: Settings, - private val attachmentsRepository: AttachmentsRepository, - private val languagesRepository: LanguagesRepository, - private val toolsRepository: ToolsRepository, - private val translationsRepository: TranslationsRepository, - private val userCountersRepository: UserCountersRepository, -) { +interface ToolCardPresenter { @Composable - @OptIn(ExperimentalCoroutinesApi::class) fun present( tool: Tool, customLocale: Locale? = null, loadAppLanguage: Boolean = false, secondLanguage: Language? = null, loadAvailableLanguages: Boolean = false, - eventSink: (ToolCard.Event) -> Unit = {}, - ): ToolCard.State { - val coroutineScope = rememberCoroutineScope() - val toolCode by rememberUpdatedState(tool.code) - - // App Translation - val appLocale by settings.produceAppLocaleState() - val appTranslationFlow = remember { - combine( - snapshotFlow { toolCode }, - snapshotFlow { appLocale }, - ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } - .flatMapLatest { it } - .shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1) - } - val appTranslation by appTranslationFlow.collectAsState(null) - val appLanguage = if (loadAppLanguage) languagesRepository.rememberLanguage(appLocale) else null - - // Custom Translation - val customLocaleFlow = rememberStateFlow(customLocale) - val customLanguage = languagesRepository.rememberLanguage(customLocale) - val customTranslationFlow = remember(customLocaleFlow) { - combine( - snapshotFlow { toolCode }, - customLocaleFlow - ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } - .flatMapLatest { it } - .shareIn(coroutineScope, SharingStarted.WhileSubscribed(5_000), 1) - } - val customTranslation by customTranslationFlow.collectAsState(null) - - // Default Translation - val defaultLocale by rememberUpdatedState(tool.defaultLocale) - val defaultTranslationFlow = remember { - combine( - snapshotFlow { toolCode }, - snapshotFlow { defaultLocale } - ) { t, l -> translationsRepository.findLatestTranslationFlow(t, l) } - .flatMapLatest { it } - .onStart { emit(null) } - } - - // Primary Translation - var isLoaded by remember { mutableStateOf(false) } - val translation by remember { - combine( - customLocaleFlow.flatMapLatest { if (it != null) customTranslationFlow else appTranslationFlow }, - defaultTranslationFlow - ) { t1, t2 -> t1 ?: t2 } - .onEach { isLoaded = true } - }.collectAsState(null) - - // Second Translation - val secondTranslation by translationsRepository.produceLatestTranslationState(toolCode, secondLanguage?.code) - val secondLanguageAvailable by remember { derivedStateOf { secondTranslation != null } } - - // eventSink - val interceptingEventSink: (ToolCard.Event) -> Unit = remember(eventSink) { - { - when (it) { - ToolCard.Event.PinTool -> coroutineScope.launch(NonCancellable) { - toolCode?.let { toolsRepository.pinTool(it) } - } - - ToolCard.Event.UnpinTool -> coroutineScope.launch(NonCancellable) { - toolCode?.let { toolsRepository.unpinTool(it) } - } - - else -> eventSink(it) - } + eventSink: (ToolCardEvent) -> Unit = {}, + ): UiState + + data class UiState( + val toolCode: String? = null, + val tool: Tool? = null, + val isLoaded: Boolean = true, + val banner: File? = null, + val language: Language? = null, + val languageAvailable: Boolean = false, + val translation: Translation? = null, + val appLanguage: Language? = null, + val appLanguageAvailable: Boolean = false, + val secondLanguage: Language? = null, + val secondLanguageAvailable: Boolean = false, + val progress: Progress? = null, + val availableLanguages: Int = 0, + val downloadProgress: DownloadProgress? = null, + val eventSink: (UiEvent) -> Unit = {}, + ) : CircuitUiState { + sealed interface Progress { + val progress: Double + + @JvmInline + value class InProgress(override val progress: Double) : Progress + data object Completed : Progress { + override val progress = 1.0 } } - - return ToolCard.State( - toolCode = toolCode, - tool = tool, - isLoaded = isLoaded, - banner = attachmentsRepository.rememberAttachmentFile(fileSystem, tool.bannerId), - language = when (customLocale) { - null -> appLanguage - else -> customLanguage - }, - languageAvailable = when (customLocale) { - null -> appTranslation != null - else -> customTranslation != null - }, - translation = translation, - appLanguage = appLanguage, - appLanguageAvailable = appTranslation != null, - secondLanguage = secondLanguage, - secondLanguageAvailable = secondLanguageAvailable, - progress = rememberProgress(toolCode, tool.progress), - availableLanguages = when { - !loadAvailableLanguages -> 0 - else -> toolCode?.let { rememberAvailableLanguages(it) } ?: 0 - }, - eventSink = interceptingEventSink, - ) } - @Composable - private fun rememberProgress(toolCode: String?, progress: Double?): ToolCard.State.Progress? { - val completed by remember(toolCode) { - when { - toolCode == null -> flowOf(false) - else -> userCountersRepository.findCounterFlow(LESSON_COMPLETION(toolCode)).map { (it?.count ?: 0) > 0 } - } - }.collectAsState(false) - - return when { - completed -> ToolCard.State.Progress.Completed - progress != null -> ToolCard.State.Progress.InProgress(progress) - else -> null - } + sealed interface UiEvent : CircuitUiEvent { + data object PinTool : UiEvent + data object UnpinTool : UiEvent } - @Composable - private fun rememberAvailableLanguages(tool: String) = remember(tool) { - translationsRepository.getTranslationsFlowForTool(tool) - .map { it.distinctBy { it.languageCode }.count() } - .distinctUntilChanged() - }.collectAsState(0).value + sealed interface ToolCardEvent : UiEvent { + data object Click : ToolCardEvent + data object OpenTool : ToolCardEvent + data object OpenToolDetails : ToolCardEvent + } } diff --git a/app/src/main/kotlin/org/cru/godtools/ui/tools/VariantToolCard.kt b/app/src/main/kotlin/org/cru/godtools/ui/tools/VariantToolCard.kt index 958153c1ef..68127e6300 100644 --- a/app/src/main/kotlin/org/cru/godtools/ui/tools/VariantToolCard.kt +++ b/app/src/main/kotlin/org/cru/godtools/ui/tools/VariantToolCard.kt @@ -21,11 +21,14 @@ import androidx.compose.ui.unit.dp import org.cru.godtools.R import org.cru.godtools.base.ui.util.ProvideLayoutDirectionFromLocale import org.cru.godtools.model.getTagline +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState internal const val TEST_TAG_SECOND_LANGUAGE_AVAILABILITY = "second_language_availability" @Composable -internal fun VariantToolCard(state: ToolCard.State, modifier: Modifier = Modifier, isSelected: Boolean = false) { +internal fun VariantToolCard(state: UiState, modifier: Modifier = Modifier, isSelected: Boolean = false) { val tool by rememberUpdatedState(state.tool) val translation by rememberUpdatedState(state.translation) val appLanguage by rememberUpdatedState(state.appLanguage) @@ -39,7 +42,7 @@ internal fun VariantToolCard(state: ToolCard.State, modifier: Modifier = Modifie ProvideLayoutDirectionFromLocale(locale = translation?.languageCode) { ElevatedCard( elevation = toolCardElevation, - onClick = { eventSink(ToolCard.Event.Click) }, + onClick = { eventSink(ToolCardEvent.Click) }, modifier = modifier ) { ToolBanner( @@ -49,7 +52,7 @@ internal fun VariantToolCard(state: ToolCard.State, modifier: Modifier = Modifie .aspectRatio(335f / 87f) ) Row(modifier = Modifier.padding(16.dp)) { - RadioButton(selected = isSelected, onClick = { eventSink(ToolCard.Event.Click) }) + RadioButton(selected = isSelected, onClick = { eventSink(ToolCardEvent.Click) }) Column(modifier = Modifier.padding(start = 16.dp)) { ToolName(state) diff --git a/app/src/test/kotlin/org/cru/godtools/ui/tools/FakeToolCardPresenter.kt b/app/src/test/kotlin/org/cru/godtools/ui/tools/FakeToolCardPresenter.kt new file mode 100644 index 0000000000..df0d0ff08b --- /dev/null +++ b/app/src/test/kotlin/org/cru/godtools/ui/tools/FakeToolCardPresenter.kt @@ -0,0 +1,36 @@ +package org.cru.godtools.ui.tools + +import androidx.compose.runtime.Composable +import java.util.Locale +import org.cru.godtools.model.Language +import org.cru.godtools.model.Tool +import org.cru.godtools.model.randomTranslation +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState + +class FakeToolCardPresenter( + var buildUiState: (Tool, Locale?, (UiEvent) -> Unit) -> UiState = { tool, customLocale, eventSink -> + UiState( + tool = tool, + toolCode = tool.code, + translation = randomTranslation(toolCode = tool.code, languageCode = customLocale ?: Locale.ENGLISH), + eventSink = eventSink + ) + }, +) : ToolCardPresenter { + @Composable + override fun present( + tool: Tool, + customLocale: Locale?, + loadAppLanguage: Boolean, + secondLanguage: Language?, + loadAvailableLanguages: Boolean, + eventSink: (ToolCardEvent) -> Unit + ): UiState = buildUiState(tool, customLocale) { + when (it) { + is ToolCardEvent -> eventSink(it) + else -> Unit + } + } +} diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenterTest.kt index f877622f9c..f42a8ae0f3 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/AllFavoritesPresenterTest.kt @@ -4,7 +4,6 @@ import android.app.Application import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.jeppeman.mockposable.mockk.everyComposable import com.slack.circuit.test.FakeNavigator import com.slack.circuit.test.test import com.slack.circuitx.android.IntentScreen @@ -33,11 +32,12 @@ import org.cru.godtools.base.ui.circuit.screen.dashboard.page.AllFavoritesScreen import org.cru.godtools.db.repository.ToolsRepository import org.cru.godtools.model.Tool import org.cru.godtools.model.randomTool -import org.cru.godtools.model.randomTranslation import org.cru.godtools.ui.dashboard.home.AllFavoritesPresenter.UiEvent import org.cru.godtools.ui.tooldetails.ToolDetailsScreen +import org.cru.godtools.ui.tools.FakeToolCardPresenter import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus import org.junit.runner.RunWith @@ -55,18 +55,13 @@ class AllFavoritesPresenterTest { coEvery { storeToolOrder(any()) } just Runs } - private val toolCardPresenter: ToolCardPresenter = mockk { - everyComposable { - present(tool = any(), eventSink = any()) - }.answers { ToolCard.State(toolCode = firstArg().code, eventSink = arg(5)) } - } private val navigator = FakeNavigator(AllFavoritesScreen) private val presenter = AllFavoritesPresenter( context = context, eventBus = eventBus, - toolCardPresenter = toolCardPresenter, + toolCardPresenter = FakeToolCardPresenter(), toolsRepository = toolsRepository, navigator = navigator, ) @@ -142,21 +137,14 @@ class AllFavoritesPresenterTest { coVerify { toolsRepository.storeToolOrder(listOf("tool1", "tool3", "tool2", "tool4")) } } - // region ToolCard.Event.Click + // region ToolCardEvent.Click @Test fun `ToolCard - Event - Click`() = runTest { val tool = randomTool("tool", Tool.Type.TRACT, primaryLocale = null, parallelLocale = null) toolsFlow.value = listOf(tool) - everyComposable { toolCardPresenter.present(tool = tool, eventSink = any()) }.answers { - ToolCard.State( - toolCode = firstArg().code, - translation = randomTranslation(languageCode = Locale.ENGLISH), - eventSink = arg(5) - ) - } presenter.test { - expectMostRecentItem().tools[0].eventSink(ToolCard.Event.Click) + expectMostRecentItem().tools[0].eventSink(ToolCardEvent.Click) val expected = context.createToolIntent( tool, @@ -173,16 +161,9 @@ class AllFavoritesPresenterTest { fun `ToolCard - Event - Click - Saved Languages`() = runTest { val tool = randomTool("tool", Tool.Type.TRACT, primaryLocale = Locale.FRENCH, parallelLocale = Locale.GERMAN) toolsFlow.value = listOf(tool) - everyComposable { toolCardPresenter.present(tool = tool, eventSink = any()) }.answers { - ToolCard.State( - toolCode = firstArg().code, - translation = randomTranslation(languageCode = Locale.ENGLISH), - eventSink = arg(5) - ) - } presenter.test { - expectMostRecentItem().tools[0].eventSink(ToolCard.Event.Click) + expectMostRecentItem().tools[0].eventSink(ToolCardEvent.Click) val expected = context.createToolIntent( tool, @@ -194,7 +175,7 @@ class AllFavoritesPresenterTest { verifyAll { eventBus.post(OpenAnalyticsActionEvent(ACTION_OPEN_TOOL, tool.code, SOURCE_FAVORITE)) } } - // endregion ToolCard.Event.Click + // endregion ToolCardEvent.Click @Test fun `ToolCard - Event - OpenToolDetails`() = runTest { @@ -202,7 +183,7 @@ class AllFavoritesPresenterTest { toolsFlow.value = listOf(tool) presenter.test { - expectMostRecentItem().tools[0].eventSink(ToolCard.Event.OpenToolDetails) + expectMostRecentItem().tools[0].eventSink(ToolCardEvent.OpenToolDetails) assertEquals(ToolDetailsScreen(tool.code!!), navigator.awaitNextScreen()) } diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenterTest.kt index 17f7f4441a..2915585dae 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/home/HomePresenterTest.kt @@ -5,7 +5,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.jeppeman.mockposable.mockk.everyComposable import com.slack.circuit.runtime.Navigator import com.slack.circuit.test.FakeNavigator import com.slack.circuit.test.test @@ -38,8 +37,9 @@ import org.cru.godtools.ui.banner.FakeBannerPresenter import org.cru.godtools.ui.banner.tutorial.TutorialFeaturesBannerPresenter import org.cru.godtools.ui.dashboard.home.HomePresenter.UiEvent import org.cru.godtools.ui.tooldetails.ToolDetailsScreen -import org.cru.godtools.ui.tools.ToolCard +import org.cru.godtools.ui.tools.FakeToolCardPresenter import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus import org.junit.runner.RunWith @@ -60,15 +60,7 @@ class HomePresenterTest { every { getLessonsFlow() } returns lessonsFlow every { getFavoriteToolsFlow() } returns toolsFlow } - private val toolCardPresenter: ToolCardPresenter = mockk { - everyComposable { present(tool = any(), eventSink = any()) }.answers { - ToolCard.State( - toolCode = firstArg().code, - translation = randomTranslation(languageCode = Locale.ENGLISH), - eventSink = arg(5) - ) - } - } + private val toolCardPresenter = FakeToolCardPresenter() private val tutorialFeaturesBannerPresenter = FakeBannerPresenter() private val navigator = FakeNavigator(HomeScreen) @@ -182,13 +174,13 @@ class HomePresenterTest { fun `State - spotlightLessons - Event - Click`() = runTest { val lesson = randomTool(type = Tool.Type.LESSON, isHidden = false, isSpotlight = true) val translation = randomTranslation(lesson.code, languageCode = Locale.FRENCH) - everyComposable { toolCardPresenter.present(tool = lesson, eventSink = any()) }.answers { - ToolCard.State(toolCode = lesson.code, translation = translation, eventSink = arg(5)) + toolCardPresenter.buildUiState = { t, _, es -> + ToolCardPresenter.UiState(toolCode = t.code, translation = translation, eventSink = es) } lessonsFlow.emit(listOf(lesson)) presenter.test { - expectMostRecentItem().spotlightLessons[0].eventSink(ToolCard.Event.Click) + expectMostRecentItem().spotlightLessons[0].eventSink(ToolCardEvent.Click) assertIs(navigator.awaitNextScreen()).let { val expected = context.createToolIntent(lesson, listOf(translation.languageCode), resumeProgress = true) @@ -231,7 +223,7 @@ class HomePresenterTest { presenter.test { toolsFlow.emit(listOf(tool)) assertNotNull(expectMostRecentItem().favoriteTools[0]) { toolState -> - toolState.eventSink(ToolCard.Event.Click) + toolState.eventSink(ToolCardEvent.Click) val expected = context.createToolIntent( tool, @@ -250,7 +242,7 @@ class HomePresenterTest { presenter.test { toolsFlow.emit(listOf(tool)) assertNotNull(expectMostRecentItem().favoriteTools[0]) { toolState -> - toolState.eventSink(ToolCard.Event.OpenTool) + toolState.eventSink(ToolCardEvent.OpenTool) val expected = context.createToolIntent( tool, @@ -269,7 +261,7 @@ class HomePresenterTest { presenter.test { toolsFlow.emit(listOf(tool)) assertNotNull(expectMostRecentItem().favoriteTools[0]) { toolState -> - toolState.eventSink(ToolCard.Event.OpenTool) + toolState.eventSink(ToolCardEvent.OpenTool) val expected = context.createToolIntent( tool, @@ -287,7 +279,7 @@ class HomePresenterTest { presenter.test { toolsFlow.emit(listOf(tool)) - expectMostRecentItem().favoriteTools[0].eventSink(ToolCard.Event.OpenToolDetails) + expectMostRecentItem().favoriteTools[0].eventSink(ToolCardEvent.OpenToolDetails) assertEquals(ToolDetailsScreen(tool.code!!), navigator.awaitNextScreen()) } diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenterTest.kt index 4790f26c4e..c49e8270dd 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/lessons/LessonsPresenterTest.kt @@ -9,7 +9,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import app.cash.turbine.ReceiveTurbine import app.cash.turbine.Turbine -import com.jeppeman.mockposable.mockk.everyComposable import com.slack.circuit.backstack.SaveableBackStack import com.slack.circuit.foundation.Circuit import com.slack.circuit.foundation.CircuitCompositionLocals @@ -49,11 +48,8 @@ import org.cru.godtools.model.Translation import org.cru.godtools.model.randomTool import org.cru.godtools.model.randomTranslation import org.cru.godtools.ui.dashboard.filters.FilterMenu -import org.cru.godtools.ui.tools.ToolCard -import org.cru.godtools.ui.tools.ToolCardPresenter -import org.cru.godtools.ui.tools.customLocaleArg -import org.cru.godtools.ui.tools.eventSinkArg -import org.cru.godtools.ui.tools.toolArg +import org.cru.godtools.ui.tools.FakeToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus import org.junit.Rule @@ -81,15 +77,6 @@ class LessonsPresenterTest { private val settings: Settings = mockk { every { appLanguageFlow } returns appLangFlow } - private val toolCardPresenter: ToolCardPresenter = mockk { - everyComposable { present(tool = any(), customLocale = any(), eventSink = any()) }.answers { - ToolCard.State( - toolCode = toolArg().code, - translation = randomTranslation(languageCode = customLocaleArg()!!), - eventSink = eventSinkArg() - ) - } - } private val toolsRepository: ToolsRepository = mockk { every { getLessonsFlow() } returns lessonsFlow @@ -108,7 +95,7 @@ class LessonsPresenterTest { eventBus = eventBus, languagesRepository = languagesRepository, settings = settings, - toolCardPresenter = toolCardPresenter, + toolCardPresenter = FakeToolCardPresenter(), toolsRepository = toolsRepository, translationsRepository = translationsRepository, ioDispatcher = UnconfinedTestDispatcher(testScope.testScheduler), @@ -383,7 +370,7 @@ class LessonsPresenterTest { ) presenter.test { - expectMostRecentItem().lessons[1].eventSink(ToolCard.Event.Click) + expectMostRecentItem().lessons[1].eventSink(ToolCardEvent.Click) val expectedIntent = context.createToolIntent( enLessonsFlow.value[1], diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt index 0bcbcc10cc..03cb95f525 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/dashboard/tools/ToolsPresenterTest.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import app.cash.turbine.ReceiveTurbine import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import com.jeppeman.mockposable.mockk.everyComposable import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.InternalCircuitApi import com.slack.circuit.test.FakeNavigator @@ -47,6 +46,7 @@ import org.cru.godtools.ui.dashboard.filters.FilterMenu import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiEvent import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState import org.cru.godtools.ui.dashboard.tools.ToolsPresenter.UiState.Mode +import org.cru.godtools.ui.tools.FakeToolCardPresenter import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardPresenter import org.junit.runner.RunWith @@ -87,11 +87,7 @@ class ToolsPresenterTest { every { getNormalToolsFlow() } returns toolsFlow } private val toolFiltersStateProducer = FakeToolFiltersStateProducer() - - private val toolCardPresenter: ToolCardPresenter = mockk { - everyComposable { present(tool = any(), secondLanguage = any(), eventSink = any()) } - .answers { ToolCard.State(tool = firstArg()) } - } + private val toolCardPresenter = FakeToolCardPresenter() private val presenter = ToolsPresenter( eventBus = mockk(), diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayoutPaparazziTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayoutPaparazziTest.kt index ffbec07d5c..93f5e00931 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayoutPaparazziTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsLayoutPaparazziTest.kt @@ -13,7 +13,6 @@ import java.util.Locale import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -27,7 +26,6 @@ import org.cru.godtools.model.randomTool import org.cru.godtools.model.randomTranslation import org.cru.godtools.ui.drawer.DrawerMenuScreenStateTestData import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.UiState -import org.cru.godtools.ui.tools.ToolCard import org.cru.godtools.ui.tools.ToolCardStateTestData import org.junit.runner.RunWith @@ -38,7 +36,7 @@ class ToolDetailsLayoutPaparazziTest( @TestParameter accessibilityMode: AccessibilityMode, ) : BasePaparazziTest(deviceConfig = deviceConfig, nightMode = nightMode, accessibilityMode = accessibilityMode) { private val banner = ToolCardStateTestData.banner - private val variants: ImmutableList = persistentListOf( + private val variants = listOf( ToolCardStateTestData.tool.copy(secondLanguage = null), ToolCardStateTestData.tool.copy(secondLanguage = null) ) diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenterTest.kt index bc9876dc0e..0cce556e85 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tooldetails/ToolDetailsPresenterTest.kt @@ -70,8 +70,8 @@ import org.cru.godtools.tutorial.layout.TutorialScreen import org.cru.godtools.ui.drawer.DrawerMenuPresenter import org.cru.godtools.ui.drawer.DrawerMenuScreen import org.cru.godtools.ui.tooldetails.ToolDetailsScreen.UiEvent -import org.cru.godtools.ui.tools.ToolCard -import org.cru.godtools.ui.tools.ToolCardPresenter +import org.cru.godtools.ui.tools.FakeToolCardPresenter +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent import org.cru.godtools.util.createToolIntent import org.greenrobot.eventbus.EventBus import org.junit.runner.RunWith @@ -132,17 +132,6 @@ class ToolDetailsPresenterTest { every { canPinToolShortcut(any()) } returns false } private val syncService: GodToolsSyncService = mockk() - private val toolCardPresenter: ToolCardPresenter = mockk { - everyComposable { - present( - tool = any(), - loadAppLanguage = true, - secondLanguage = null, - loadAvailableLanguages = true, - eventSink = any() - ) - }.answers { ToolCard.State(toolCode = firstArg().code, eventSink = arg(5)) } - } private fun createPresenter(screen: ToolDetailsScreen = ToolDetailsScreen(TOOL)) = ToolDetailsPresenter( context = context, @@ -159,7 +148,7 @@ class ToolDetailsPresenterTest { syncService = syncService, isConnected = isConnected, drawerMenuPresenter = drawerMenuPresenter, - toolCardPresenter = toolCardPresenter, + toolCardPresenter = FakeToolCardPresenter(), screen = screen, ioDispatcher = UnconfinedTestDispatcher(testScope.testScheduler), navigator = navigator, @@ -308,11 +297,11 @@ class ToolDetailsPresenterTest { assertEquals(2, state.variants.size) assertEquals(TOOL, state.variants[0].toolCode) - state.variants[0].eventSink(ToolCard.Event.Click) + state.variants[0].eventSink(ToolCardEvent.Click) expectNoEvents() assertEquals("variant1", state.variants[1].toolCode) - state.variants[1].eventSink(ToolCard.Event.Click) + state.variants[1].eventSink(ToolCardEvent.Click) assertEquals("variant1", expectMostRecentItem().toolCode) } } @@ -326,7 +315,7 @@ class ToolDetailsPresenterTest { createPresenter().test { val state = expectMostRecentItem() - state.variants.single { it.toolCode == "variant" }.eventSink(ToolCard.Event.Click) + state.variants.single { it.toolCode == "variant" }.eventSink(ToolCardEvent.Click) assertEquals("variant", expectMostRecentItem().toolCode) } } @@ -614,7 +603,7 @@ class ToolDetailsPresenterTest { DownloadLatestTranslation(downloadManager, TOOL, Locale.ENGLISH, true) DownloadLatestTranslation(downloadManager, TOOL, Locale.FRENCH, true) } - expectMostRecentItem().variants.single { it.toolCode == "variant" }.eventSink(ToolCard.Event.Click) + expectMostRecentItem().variants.single { it.toolCode == "variant" }.eventSink(ToolCardEvent.Click) verifyComposable { DownloadLatestTranslation(downloadManager, "variant", Locale.ENGLISH, true) diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenterTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenterTest.kt similarity index 81% rename from app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenterTest.kt rename to app/src/testDebug/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenterTest.kt index a0f2a32714..5b33720a9d 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenterTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/DefaultToolCardPresenterTest.kt @@ -45,6 +45,9 @@ import org.cru.godtools.model.UserCounter import org.cru.godtools.model.randomTool import org.cru.godtools.model.randomTranslation import org.cru.godtools.shared.user.activity.UserCounterNames.LESSON_COMPLETION +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -53,7 +56,7 @@ private const val BANNER_ID = 1L @RunWith(AndroidJUnit4::class) @Config(application = Application::class) -class ToolCardPresenterTest { +class DefaultToolCardPresenterTest { private val appLocaleState = mutableStateOf(Locale.ENGLISH) private val toolFlow = MutableStateFlow(randomTool(TOOL, bannerId = BANNER_ID)) private val bannerFlow = MutableSharedFlow(extraBufferCapacity = 1) @@ -83,9 +86,9 @@ class ToolCardPresenterTest { private val userCountersRepository: UserCountersRepository = mockk { every { findCounterFlow(any()) } returns flowOf(null) } - private val events = TestEventSink() + private val events = TestEventSink() - private val presenter = ToolCardPresenter( + private val presenter = DefaultToolCardPresenter( fileSystem = fileSystem, settings = settings, attachmentsRepository = attachmentsRepository, @@ -98,9 +101,9 @@ class ToolCardPresenterTest { @AfterTest fun cleanup() = AndroidUiDispatcherUtil.runScheduledDispatches() - // region ToolCard.State.tool + // region UiState.tool @Test - fun `ToolCardState - tool`() = runTest { + fun `UiState - tool`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value) } ) { @@ -109,7 +112,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - tool - emit new state on update`() = runTest { + fun `UiState - tool - emit new state on update`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value) } ) { @@ -119,11 +122,11 @@ class ToolCardPresenterTest { assertEquals(toolFlow.value, expectMostRecentItem().tool) } } - // endregion ToolCard.State.tool + // endregion UiState.tool - // region ToolCard.State.banner + // region UiState.banner @Test - fun `ToolCardState - banner`() = runTest { + fun `UiState - banner`() = runTest { val banner = Attachment(BANNER_ID) { sha256 = "0123456789abcdef" isDownloaded = true @@ -141,7 +144,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - banner - don't return banners not downloaded yet`() = runTest { + fun `UiState - banner - don't return banners not downloaded yet`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value) } ) { @@ -161,7 +164,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - banner - emit new state on Attachment update`() = runTest { + fun `UiState - banner - emit new state on Attachment update`() = runTest { val banner = Attachment(BANNER_ID) { sha256 = "0123456789abcdef" isDownloaded = true @@ -180,11 +183,11 @@ class ToolCardPresenterTest { assertEquals(file, expectMostRecentItem().banner) } } - // endregion ToolCard.State.banner + // endregion UiState.banner - // region ToolCard.State.isLoaded + // region UiState.isLoaded @Test - fun `ToolCardState - isLoaded`() = runTest { + fun `UiState - isLoaded`() = runTest { toolFlow.value = randomTool(TOOL, defaultLocale = Locale.FRENCH) presenterTestOf( @@ -201,11 +204,11 @@ class ToolCardPresenterTest { assertTrue(expectMostRecentItem().isLoaded) } } - // endregion ToolCard.State.isLoaded + // endregion UiState.isLoaded - // region ToolCard.State.translation + // region UiState.translation @Test - fun `ToolCardState - translation`() = runTest { + fun `UiState - translation`() = runTest { toolFlow.value = randomTool(TOOL) appLocaleState.value = Locale.FRENCH val translation = randomTranslation(TOOL, Locale.FRENCH) @@ -222,7 +225,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - translation - fallback to default language`() = runTest { + fun `UiState - translation - fallback to default language`() = runTest { toolFlow.value = randomTool(TOOL, defaultLocale = Locale.ENGLISH) appLocaleState.value = Locale.FRENCH val translation = randomTranslation(TOOL, Locale.ENGLISH) @@ -240,7 +243,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - translation - don't emit fallback if primary hasn't loaded yet`() = runTest { + fun `UiState - translation - don't emit fallback if primary hasn't loaded yet`() = runTest { toolFlow.value = randomTool(TOOL, defaultLocale = Locale.ENGLISH) appLocaleState.value = Locale.FRENCH val translation = randomTranslation(TOOL, Locale.ENGLISH) @@ -255,7 +258,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - translation - custom locale`() = runTest { + fun `UiState - translation - custom locale`() = runTest { toolFlow.value = randomTool(TOOL) appLocaleState.value = Locale.ENGLISH val translation = randomTranslation(TOOL, Locale.FRENCH) @@ -274,7 +277,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - translation - custom locale - fallback to default language`() = runTest { + fun `UiState - translation - custom locale - fallback to default language`() = runTest { toolFlow.value = randomTool(TOOL, defaultLocale = Locale.ENGLISH) appLocaleState.value = Locale.GERMAN val translation = randomTranslation(TOOL, Locale.ENGLISH) @@ -300,11 +303,11 @@ class ToolCardPresenterTest { } } } - // endregion ToolCard.State.translation + // endregion UiState.translation - // region ToolCard.State.appLanguage + // region UiState.appLanguage @Test - fun `ToolCardState - appLanguage`() = runTest { + fun `UiState - appLanguage`() = runTest { toolFlow.value = randomTool(TOOL) appLocaleState.value = Locale.FRENCH @@ -317,7 +320,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - appLanguage - loadAppLanguage=false`() = runTest { + fun `UiState - appLanguage - loadAppLanguage=false`() = runTest { toolFlow.value = randomTool(TOOL) appLocaleState.value = Locale.FRENCH @@ -330,11 +333,11 @@ class ToolCardPresenterTest { verifyAll { languagesRepository wasNot Called } } - // endregion ToolCard.State.appLanguage + // endregion UiState.appLanguage - // region ToolCard.State.appLanguageAvailable + // region UiState.appLanguageAvailable @Test - fun `ToolCardState - appLanguageAvailable`() = runTest { + fun `UiState - appLanguageAvailable`() = runTest { toolFlow.value = randomTool(TOOL) appLocaleState.value = Locale.FRENCH val translation = randomTranslation(TOOL, Locale.FRENCH) @@ -348,11 +351,11 @@ class ToolCardPresenterTest { assertTrue(expectMostRecentItem().appLanguageAvailable) } } - // endregion ToolCard.State.appLanguageAvailable + // endregion UiState.appLanguageAvailable - // region ToolCard.State.secondLanguage + // region UiState.secondLanguage @Test - fun `ToolCardState - secondLanguage`() = runTest { + fun `UiState - secondLanguage`() = runTest { toolFlow.value = randomTool(TOOL) val language = Language(Locale.FRENCH) @@ -362,11 +365,11 @@ class ToolCardPresenterTest { assertEquals(language, expectMostRecentItem().secondLanguage) } } - // endregion ToolCard.State.secondLanguage + // endregion UiState.secondLanguage - // region ToolCard.State.secondLanguageAvailable + // region UiState.secondLanguageAvailable @Test - fun `ToolCardState - secondLanguageAvailable`() = runTest { + fun `UiState - secondLanguageAvailable`() = runTest { toolFlow.value = randomTool(TOOL) val language = Language(Locale.FRENCH) val translation = randomTranslation(TOOL, Locale.FRENCH) @@ -378,11 +381,11 @@ class ToolCardPresenterTest { assertTrue(expectMostRecentItem().secondLanguageAvailable) } } - // endregion ToolCard.State.secondLanguageAvailable + // endregion UiState.secondLanguageAvailable - // region ToolCard.State.progress + // region UiState.progress @Test - fun `ToolCardState - progress - not started`() = runTest { + fun `UiState - progress - not started`() = runTest { val tool = randomTool(TOOL, Tool.Type.LESSON, progress = null) presenterTestOf(presentFunction = { presenter.present(tool) }) { @@ -391,34 +394,34 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - progress - in progress`() = runTest { + fun `UiState - progress - in progress`() = runTest { val tool = randomTool(TOOL, Tool.Type.LESSON, progress = Random.nextDouble(0.0, 1.0)) presenterTestOf(presentFunction = { presenter.present(tool) }) { assertEquals( tool.progress!!, - assertIs(expectMostRecentItem().progress).progress, + assertIs(expectMostRecentItem().progress).progress, 0.0001 ) } } @Test - fun `ToolCardState - progress - completed`() = runTest { + fun `UiState - progress - completed`() = runTest { val tool = randomTool(TOOL, Tool.Type.LESSON, progress = Random.nextDouble(0.0, 1.0)) every { userCountersRepository.findCounterFlow(LESSON_COMPLETION(TOOL)) } returns flowOf(UserCounter(apiCount = 1)) presenterTestOf(presentFunction = { presenter.present(tool) }) { - assertEquals(ToolCard.State.Progress.Completed, expectMostRecentItem().progress) + assertEquals(UiState.Progress.Completed, expectMostRecentItem().progress) } } - // endregion ToolCard.State.progress + // endregion UiState.progress - // region ToolCard.State.availableLanguages + // region UiState.availableLanguages @Test - fun `ToolCardState - availableLanguages`() = runTest { + fun `UiState - availableLanguages`() = runTest { toolFlow.value = randomTool(TOOL) val translations = listOf( randomTranslation(languageCode = Locale.ENGLISH), @@ -438,7 +441,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - availableLanguages - loadAvailableLanguages=false`() = runTest { + fun `UiState - availableLanguages - loadAvailableLanguages=false`() = runTest { toolFlow.value = randomTool(TOOL) val translations = listOf( randomTranslation(languageCode = Locale.ENGLISH), @@ -458,7 +461,7 @@ class ToolCardPresenterTest { } @Test - fun `ToolCardState - availableLanguages - Only distinct languages are counted`() = runTest { + fun `UiState - availableLanguages - Only distinct languages are counted`() = runTest { toolFlow.value = randomTool(TOOL) every { translationsRepository.getTranslationsFlowForTool(TOOL) }.returns( flowOf( @@ -479,11 +482,11 @@ class ToolCardPresenterTest { verify { translationsRepository.getTranslationsFlowForTool(TOOL) } } - // endregion ToolCard.State.availableLanguages + // endregion UiState.availableLanguages - // region ToolCard.State + // region UiState @Test - fun `ToolCardState - GT-2364 - App Language Not Available, Second language matches Default language`() = runTest { + fun `UiState - GT-2364 - App Language Not Available, Second language matches Default language`() = runTest { appLocaleState.value = Locale.FRENCH toolFlow.value = randomTool(TOOL, defaultLocale = Locale.ENGLISH) val translation = randomTranslation(TOOL, Locale.ENGLISH) @@ -509,72 +512,72 @@ class ToolCardPresenterTest { } } } - // endregion ToolCard.State + // endregion UiState - // region ToolCard.Event.Click + // region ToolCardEvent.Click @Test fun `ToolCardEvent - Click`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value, eventSink = events) } ) { - expectMostRecentItem().eventSink(ToolCard.Event.Click) + expectMostRecentItem().eventSink(ToolCardEvent.Click) } - events.assertEvent(ToolCard.Event.Click) + events.assertEvent(ToolCardEvent.Click) } - // endregion ToolCard.Event.Click + // endregion ToolCardEvent.Click - // region ToolCard.Event.OpenTool + // region ToolCardEvent.OpenTool @Test fun `ToolCardEvent - OpenTool`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value, eventSink = events) } ) { - expectMostRecentItem().eventSink(ToolCard.Event.OpenTool) + expectMostRecentItem().eventSink(ToolCardEvent.OpenTool) } - events.assertEvent(ToolCard.Event.OpenTool) + events.assertEvent(ToolCardEvent.OpenTool) } - // endregion ToolCard.Event.OpenTool + // endregion ToolCardEvent.OpenTool - // region ToolCard.Event.OpenToolDetails + // region ToolCardEvent.OpenToolDetails @Test fun `ToolCardEvent - OpenToolDetails`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value, eventSink = events) } ) { - expectMostRecentItem().eventSink(ToolCard.Event.OpenToolDetails) + expectMostRecentItem().eventSink(ToolCardEvent.OpenToolDetails) } - events.assertEvent(ToolCard.Event.OpenToolDetails) + events.assertEvent(ToolCardEvent.OpenToolDetails) } - // endregion ToolCard.Event.OpenToolDetails + // endregion ToolCardEvent.OpenToolDetails - // region ToolCard.Event.PinTool + // region UiEvent.PinTool @Test - fun `ToolCardEvent - PinTool`() = runTest { + fun `UiEvent - PinTool`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value, eventSink = events) } ) { - expectMostRecentItem().eventSink(ToolCard.Event.PinTool) + expectMostRecentItem().eventSink(UiEvent.PinTool) } coVerifyAll { toolsRepository.pinTool(TOOL) } events.assertNoEvents() } - // endregion ToolCard.Event.PinTool + // endregion UiEvent.PinTool - // region ToolCard.Event.UnpinTool + // region UiEvent.UnpinTool @Test - fun `ToolCardEvent - UnpinTool`() = runTest { + fun `UiEvent - UnpinTool`() = runTest { presenterTestOf( presentFunction = { presenter.present(tool = toolFlow.collectAsState().value, eventSink = events) } ) { - expectMostRecentItem().eventSink(ToolCard.Event.UnpinTool) + expectMostRecentItem().eventSink(UiEvent.UnpinTool) } coVerifyAll { toolsRepository.unpinTool(TOOL) } events.assertNoEvents() } - // endregion ToolCard.Event.UnpinTool + // endregion UiEvent.UnpinTool } diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/FavoriteActionTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/FavoriteActionTest.kt index ce289c5e51..00e3ac0a99 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/FavoriteActionTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/FavoriteActionTest.kt @@ -13,6 +13,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.slack.circuit.test.TestEventSink import kotlin.test.Test import org.cru.godtools.model.randomTool +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState import org.junit.Rule import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -23,24 +25,24 @@ class FavoriteActionTest { @get:Rule val composeTestRule = createComposeRule() - private val events = TestEventSink() + private val events = TestEventSink() // region FavoriteAction() @Test fun `FavoriteAction() - add to favorites`() { - val state = ToolCard.State( + val state = UiState( tool = randomTool(isFavorite = false), eventSink = events, ) composeTestRule.setContent { FavoriteAction(state) } composeTestRule.onRoot().performClick() - events.assertEvent(ToolCard.Event.PinTool) + events.assertEvent(UiEvent.PinTool) } @Test fun `FavoriteAction() - remove from favorites`() { - val state = ToolCard.State( + val state = UiState( tool = randomTool(isFavorite = true), eventSink = events, ) @@ -48,12 +50,12 @@ class FavoriteActionTest { composeTestRule.onRoot().performClick() composeTestRule.onNode(isDialog()).assertDoesNotExist() - events.assertEvent(ToolCard.Event.UnpinTool) + events.assertEvent(UiEvent.UnpinTool) } @Test fun `FavoriteAction() - remove from favorites - confirmRemoval - confirm`() { - val state = ToolCard.State( + val state = UiState( tool = randomTool(isFavorite = true), eventSink = events, ) @@ -65,12 +67,12 @@ class FavoriteActionTest { composeTestRule.onNode(hasAnyAncestor(isDialog()) and hasClickAction() and hasText("Remove")).performClick() composeTestRule.onNode(isDialog()).assertDoesNotExist() - events.assertEvent(ToolCard.Event.UnpinTool) + events.assertEvent(UiEvent.UnpinTool) } @Test fun `FavoriteAction() - remove from favorites - confirmRemoval - cancel`() { - val state = ToolCard.State( + val state = UiState( tool = randomTool(isFavorite = true), eventSink = events, ) diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/LessonToolCardPaparazziTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/LessonToolCardPaparazziTest.kt index 253f77093a..828638535a 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/LessonToolCardPaparazziTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/LessonToolCardPaparazziTest.kt @@ -91,7 +91,7 @@ class LessonToolCardPaparazziTest( @Test fun `LessonToolCard() - Progress - Completed`() = centerInSnapshot(Modifier.fillMaxSize()) { - LessonToolCard(toolState.copy(progress = ToolCard.State.Progress.Completed), showProgress = true) + LessonToolCard(toolState.copy(progress = ToolCardPresenter.UiState.Progress.Completed), showProgress = true) } @Test diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/SquareToolCardTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/SquareToolCardTest.kt index 31b0b5c6c4..f3601cec25 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/SquareToolCardTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/SquareToolCardTest.kt @@ -15,6 +15,9 @@ import kotlin.test.Test import kotlin.test.assertEquals import org.cru.godtools.downloadmanager.DownloadProgress import org.cru.godtools.model.randomTool +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState import org.junit.Rule import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -25,13 +28,13 @@ class SquareToolCardTest { @get:Rule val composeTestRule = createComposeRule() - private val events = TestEventSink() + private val events = TestEventSink() @Test fun `SquareToolCard()`() { val tool = randomTool(name = UUID.randomUUID().toString()) - composeTestRule.setContent { SquareToolCard(ToolCard.State(tool = tool)) } + composeTestRule.setContent { SquareToolCard(UiState(tool = tool)) } composeTestRule.onNodeWithText(tool.name!!).assertExists() composeTestRule.onNodeWithTag(TEST_TAG_FAVORITE_ACTION).assertExists() @@ -39,10 +42,10 @@ class SquareToolCardTest { @Test fun `SquareToolCard() - Event - Click`() { - composeTestRule.setContent { SquareToolCard(ToolCard.State(eventSink = events)) } + composeTestRule.setContent { SquareToolCard(UiState(eventSink = events)) } composeTestRule.onRoot().performClick() - events.assertEvent(ToolCard.Event.Click) + events.assertEvent(ToolCardEvent.Click) } // region SquareToolCard - Category @@ -50,7 +53,7 @@ class SquareToolCardTest { fun `SquareToolCard(showCategory=true)`() { composeTestRule.setContent { SquareToolCard( - state = ToolCard.State(tool = randomTool(category = "gospel")), + state = UiState(tool = randomTool(category = "gospel")), showCategory = true ) } @@ -62,7 +65,7 @@ class SquareToolCardTest { fun `SquareToolCard(showCategory=false)`() { composeTestRule.setContent { SquareToolCard( - state = ToolCard.State(tool = randomTool(category = "gospel")), + state = UiState(tool = randomTool(category = "gospel")), showCategory = false ) } @@ -75,7 +78,7 @@ class SquareToolCardTest { @Test fun `SquareToolCard() - Download Progress - Hidden when not downloading`() { composeTestRule.setContent { - SquareToolCard(state = ToolCard.State(tool = randomTool(), downloadProgress = null)) + SquareToolCard(UiState(tool = randomTool(), downloadProgress = null)) } composeTestRule @@ -86,7 +89,7 @@ class SquareToolCardTest { @Test fun `SquareToolCard() - Download Progress - Visible when downloading`() { composeTestRule.setContent { - SquareToolCard(state = ToolCard.State(tool = randomTool(), downloadProgress = DownloadProgress(1, 4))) + SquareToolCard(UiState(tool = randomTool(), downloadProgress = DownloadProgress(1, 4))) } val progressRangeInfo = composeTestRule diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardActionsTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardActionsTest.kt index 60e1e94097..5f823e76bc 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardActionsTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardActionsTest.kt @@ -10,6 +10,9 @@ import com.slack.circuit.test.TestEventSink import kotlin.test.Test import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState import org.junit.Rule import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -20,41 +23,41 @@ class ToolCardActionsTest { @get:Rule val composeTestRule = createComposeRule() - private val events = TestEventSink() + private val events = TestEventSink() @Test fun `Button - Open Tool`() { - val state = ToolCard.State(eventSink = events) + val state = UiState(eventSink = events) composeTestRule.setContent { ToolCardActions(state) } composeTestRule.onNodeWithText("Open", substring = true, ignoreCase = true).performClick() - events.assertEvent(ToolCard.Event.OpenTool) + events.assertEvent(ToolCardEvent.OpenTool) } @Test fun `Button - Tool Details`() { - val state = ToolCard.State(eventSink = events) + val state = UiState(eventSink = events) composeTestRule.setContent { ToolCardActions(state) } composeTestRule.onNodeWithText("Details", substring = true, ignoreCase = true).performClick() - events.assertEvent(ToolCard.Event.OpenToolDetails) + events.assertEvent(ToolCardEvent.OpenToolDetails) } @Test fun `Recompose - eventSink updates`() = runTest { - val stateFlow = MutableStateFlow(ToolCard.State()) + val stateFlow = MutableStateFlow(UiState()) composeTestRule.setContent { ToolCardActions(stateFlow.collectAsState().value) } composeTestRule.onNodeWithText("Open", substring = true, ignoreCase = true).performClick() composeTestRule.onNodeWithText("Details", substring = true, ignoreCase = true).performClick() events.assertNoEvents() - stateFlow.value = ToolCard.State(eventSink = events) + stateFlow.value = UiState(eventSink = events) composeTestRule.onNodeWithText("Open", substring = true, ignoreCase = true).performClick() composeTestRule.onNodeWithText("Details", substring = true, ignoreCase = true).performClick() events.assertEvents( - ToolCard.Event.OpenTool, - ToolCard.Event.OpenToolDetails + ToolCardEvent.OpenTool, + ToolCardEvent.OpenToolDetails ) } } diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter+Mocks.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter+Mocks.kt deleted file mode 100644 index f5e97ae2ce..0000000000 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardPresenter+Mocks.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.cru.godtools.ui.tools - -import io.mockk.MockKAnswerScope -import java.util.Locale -import org.cru.godtools.model.Tool -import org.cru.godtools.ui.tools.ToolCard.Event - -fun MockKAnswerScope.toolArg() = arg(0) -fun MockKAnswerScope.customLocaleArg() = arg(1) -fun MockKAnswerScope.eventSinkArg() = arg<(Event) -> Unit>(5) diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardStateTestData.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardStateTestData.kt index d4e1e97fc3..fb0858deac 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardStateTestData.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/ToolCardStateTestData.kt @@ -7,6 +7,7 @@ import org.cru.godtools.model.Language import org.cru.godtools.model.Tool import org.cru.godtools.model.randomTool import org.cru.godtools.model.randomTranslation +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState object ToolCardStateTestData { val banner: File = File.createTempFile("tool", "png") @@ -14,7 +15,7 @@ object ToolCardStateTestData { Drawable.createFromStream(this.javaClass.getResourceAsStream("banner.jpg"), "banner.jpg")!! } - val tool = ToolCard.State( + val tool = UiState( tool = randomTool( code = "tool", name = "Tool Title", @@ -34,7 +35,7 @@ object ToolCardStateTestData { appLanguageAvailable = true, secondLanguage = Language(Locale.FRENCH), secondLanguageAvailable = true, - progress = ToolCard.State.Progress.InProgress(0.6), + progress = UiState.Progress.InProgress(0.6), ) val toolFavorite = tool.copy( diff --git a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/VariantToolCardTest.kt b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/VariantToolCardTest.kt index 8c5332e795..d6a637badb 100644 --- a/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/VariantToolCardTest.kt +++ b/app/src/testDebug/kotlin/org/cru/godtools/ui/tools/VariantToolCardTest.kt @@ -24,6 +24,9 @@ import org.cru.godtools.R import org.cru.godtools.model.Language import org.cru.godtools.model.randomTool import org.cru.godtools.model.randomTranslation +import org.cru.godtools.ui.tools.ToolCardPresenter.ToolCardEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiEvent +import org.cru.godtools.ui.tools.ToolCardPresenter.UiState import org.junit.Rule import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -36,14 +39,14 @@ class VariantToolCardTest { private fun stringRes(@StringRes id: Int) = composeTestRule.activity.getString(id) - private val events = TestEventSink() + private val events = TestEventSink() @Test fun `VariantToolCard()`() { val tool = randomTool(name = UUID.randomUUID().toString()) val translation = randomTranslation(tool.code, tagline = UUID.randomUUID().toString()) - composeTestRule.setContent { VariantToolCard(ToolCard.State(tool = tool, translation = translation)) } + composeTestRule.setContent { VariantToolCard(UiState(tool = tool, translation = translation)) } composeTestRule.onNodeWithText(translation.name!!).assertExists() composeTestRule.onNodeWithText(translation.tagline!!).assertExists() @@ -51,10 +54,10 @@ class VariantToolCardTest { @Test fun `VariantToolCard() - Event - Click`() { - composeTestRule.setContent { VariantToolCard(ToolCard.State(eventSink = events)) } + composeTestRule.setContent { VariantToolCard(UiState(eventSink = events)) } composeTestRule.onRoot().performClick() - events.assertEvent(ToolCard.Event.Click) + events.assertEvent(ToolCardEvent.Click) } // region VariantToolCard - isSelected @@ -62,7 +65,7 @@ class VariantToolCardTest { fun `VariantToolCard(isSelected=true)`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State(tool = randomTool(), eventSink = events), + state = UiState(tool = randomTool(), eventSink = events), isSelected = true, ) } @@ -71,14 +74,14 @@ class VariantToolCardTest { .assertExists() .assertIsSelected() .performClick() - events.assertEvent(ToolCard.Event.Click) + events.assertEvent(ToolCardEvent.Click) } @Test fun `VariantToolCard(isSelected=false)`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State(tool = randomTool(), eventSink = events), + state = UiState(tool = randomTool(), eventSink = events), isSelected = false, ) } @@ -87,7 +90,7 @@ class VariantToolCardTest { .assertExists() .assertIsNotSelected() .performClick() - events.assertEvent(ToolCard.Event.Click) + events.assertEvent(ToolCardEvent.Click) } // endregion VariantToolCard - isSelected @@ -96,7 +99,7 @@ class VariantToolCardTest { fun `VariantToolCard - App Language - Available`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State( + state = UiState( tool = randomTool(), appLanguage = Language(Locale.ENGLISH), appLanguageAvailable = true, @@ -114,7 +117,7 @@ class VariantToolCardTest { fun `VariantToolCard - App Language - Not Available`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State( + state = UiState( tool = randomTool(), appLanguage = Language(Locale.ENGLISH), appLanguageAvailable = false, @@ -134,7 +137,7 @@ class VariantToolCardTest { fun `VariantToolCard - Second Language - Available`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State( + state = UiState( tool = randomTool(), appLanguage = Language(Locale.ENGLISH), appLanguageAvailable = false, @@ -154,7 +157,7 @@ class VariantToolCardTest { fun `VariantToolCard - Second Language - Not Available`() { composeTestRule.setContent { VariantToolCard( - state = ToolCard.State( + state = UiState( tool = randomTool(), appLanguage = Language(Locale.ENGLISH), appLanguageAvailable = true,