Loading feature/funding/googleplay/src/debug/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContentPreview.kt 0 → 100644 +36 −0 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import app.k9mail.core.ui.compose.common.annotation.PreviewDevicesWithBackground import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State @Composable @PreviewDevicesWithBackground fun ContributionContentPreview() { PreviewWithTheme { ContributionContent( state = State( recurringContributions = FakeData.recurringContributions, oneTimeContributions = FakeData.oneTimeContributions, selectedContribution = FakeData.recurringContributions.first(), ), onEvent = {}, contentPadding = PaddingValues(), ) } } @Composable @Preview(showBackground = true) fun ContributionContentEmptyPreview() { PreviewWithTheme { ContributionContent( state = State(), onEvent = {}, contentPadding = PaddingValues(), ) } } feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContent.kt +46 −14 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleLarge import androidx.compose.ui.platform.testTag import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer import app.k9mail.core.ui.compose.theme2.MainTheme import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State Loading @@ -19,18 +23,46 @@ internal fun ContributionContent( contentPadding: PaddingValues, modifier: Modifier = Modifier, ) { ResponsiveWidthContainer( modifier = modifier .testTag("ContributionContent") .padding(contentPadding), ) { val scrollState = rememberScrollState() Column( modifier = modifier.fillMaxSize() .padding(contentPadding) .clickable( enabled = false, onClick = { onEvent(Event.OnPurchaseClicked) }, ), modifier = Modifier .fillMaxSize() .padding(horizontal = MainTheme.spacings.quadruple) .verticalScroll(scrollState), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.triple), ) { TextTitleLarge( text = "ContributionScreen", ContributionHeader() ContributionList( contributions = if (state.isRecurringContributionSelected) { state.recurringContributions } else { state.oneTimeContributions }, selectedItem = state.selectedContribution, isRecurringContributionSelected = state.isRecurringContributionSelected, onOneTimeContributionTypeClick = { onEvent(Event.OnOneTimeContributionSelected) }, onRecurringContributionTypeClick = { onEvent(Event.OnRecurringContributionSelected) }, onItemClick = { onEvent(Event.OnContributionItemClicked(it)) }, ) TextBodyMedium(text = "is recurring selected: ${state.isRecurringContributionSelected}") ContributionFooter( onClick = { onEvent(Event.OnPurchaseClicked) }, isPurchaseEnabled = state.selectedContribution != null, ) } } } feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContract.kt +3 −3 Original line number Diff line number Diff line Loading @@ -21,9 +21,9 @@ internal class ContributionContract { ) sealed interface Event { data object OnOneTimeContributionClicked : Event data object OnRecurringContributionClicked : Event data class OnContributionClicked(val item: Contribution) : Event data object OnOneTimeContributionSelected : Event data object OnRecurringContributionSelected : Event data class OnContributionItemClicked(val item: Contribution) : Event data object OnPurchaseClicked : Event } } feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionViewModel.kt +33 −6 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import app.k9mail.core.ui.compose.common.mvi.BaseViewModel import app.k9mail.feature.funding.googleplay.domain.entity.Contribution import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.ViewModel Loading @@ -12,14 +13,40 @@ internal class ContributionViewModel( override fun event(event: Event) { when (event) { is Event.OnContributionClicked -> TODO() Event.OnOneTimeContributionSelected -> onOneTimeContributionSelected() Event.OnRecurringContributionSelected -> onRecurringContributionSelected() is Event.OnContributionItemClicked -> onContributionItemClicked(event.item) Event.OnPurchaseClicked -> onPurchaseClicked() } } private fun onOneTimeContributionSelected() { updateState { it.copy( isRecurringContributionSelected = false, selectedContribution = it.oneTimeContributions.firstOrNull(), ) } } Event.OnOneTimeContributionClicked -> TODO() Event.OnPurchaseClicked -> { // TODO private fun onRecurringContributionSelected() { updateState { it.copy( isRecurringContributionSelected = true, selectedContribution = it.recurringContributions.firstOrNull(), ) } } Event.OnRecurringContributionClicked -> TODO() private fun onContributionItemClicked(item: Contribution) { updateState { it.copy( selectedContribution = item, ) } } private fun onPurchaseClicked() { // TODO: Implement purchase logic } } feature/funding/googleplay/src/test/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionViewModelTest.kt 0 → 100644 +118 −0 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import app.k9mail.core.ui.compose.testing.MainDispatcherRule import app.k9mail.core.ui.compose.testing.mvi.MviContext import app.k9mail.core.ui.compose.testing.mvi.MviTurbines import app.k9mail.core.ui.compose.testing.mvi.runMviTest import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck import app.k9mail.feature.funding.googleplay.domain.entity.Contribution import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State import assertk.assertThat import assertk.assertions.isEqualTo import kotlin.test.Test import org.junit.Rule class ContributionViewModelTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun `should change selected contribution and selected type when on time contribution selected`() = runMviTest { val initialState = State( isRecurringContributionSelected = true, oneTimeContributions = FakeData.oneTimeContributions, ) contributionRobot(initialState) { selectOneTimeContribution() verifyOneTimeContributionSelected() } } @Test fun `should change selected contribution and selected type when recurring contribution selected`() = runMviTest { val initialState = State( isRecurringContributionSelected = false, recurringContributions = FakeData.recurringContributions, ) contributionRobot(initialState) { selectRecurringContribution() verifyRecurringContributionSelected() } } @Test fun `should change selected contribution when contribution item clicked`() = runMviTest { val initialState = State( isRecurringContributionSelected = true, recurringContributions = FakeData.recurringContributions, ) val selectedContribution = FakeData.recurringContributions[2] contributionRobot(initialState) { selectContributionItem(selectedContribution) verifyContributionItemSelected(selectedContribution) } } } private suspend fun MviContext.contributionRobot( initialState: State = State(), interaction: suspend ContributionRobot.() -> Unit, ) = ContributionRobot(this, initialState).apply { initialize() interaction() } private class ContributionRobot( private val mviContext: MviContext, private val initialState: State = State(), ) { private val viewModel = ContributionViewModel(initialState) private lateinit var turbines: MviTurbines<State, Nothing> suspend fun initialize() { turbines = mviContext.turbinesWithInitialStateCheck(viewModel, initialState) } fun selectOneTimeContribution() { viewModel.event(Event.OnOneTimeContributionSelected) } suspend fun verifyOneTimeContributionSelected() { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( isRecurringContributionSelected = false, selectedContribution = initialState.oneTimeContributions.first(), ), ) } fun selectRecurringContribution() { viewModel.event(Event.OnRecurringContributionSelected) } suspend fun verifyRecurringContributionSelected() { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( isRecurringContributionSelected = true, selectedContribution = initialState.recurringContributions.first(), ), ) } fun selectContributionItem(item: Contribution) { viewModel.event(Event.OnContributionItemClicked(item)) } suspend fun verifyContributionItemSelected(item: Contribution) { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( selectedContribution = item, ), ) } } Loading
feature/funding/googleplay/src/debug/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContentPreview.kt 0 → 100644 +36 −0 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import app.k9mail.core.ui.compose.common.annotation.PreviewDevicesWithBackground import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State @Composable @PreviewDevicesWithBackground fun ContributionContentPreview() { PreviewWithTheme { ContributionContent( state = State( recurringContributions = FakeData.recurringContributions, oneTimeContributions = FakeData.oneTimeContributions, selectedContribution = FakeData.recurringContributions.first(), ), onEvent = {}, contentPadding = PaddingValues(), ) } } @Composable @Preview(showBackground = true) fun ContributionContentEmptyPreview() { PreviewWithTheme { ContributionContent( state = State(), onEvent = {}, contentPadding = PaddingValues(), ) } }
feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContent.kt +46 −14 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium import app.k9mail.core.ui.compose.designsystem.atom.text.TextTitleLarge import androidx.compose.ui.platform.testTag import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer import app.k9mail.core.ui.compose.theme2.MainTheme import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State Loading @@ -19,18 +23,46 @@ internal fun ContributionContent( contentPadding: PaddingValues, modifier: Modifier = Modifier, ) { ResponsiveWidthContainer( modifier = modifier .testTag("ContributionContent") .padding(contentPadding), ) { val scrollState = rememberScrollState() Column( modifier = modifier.fillMaxSize() .padding(contentPadding) .clickable( enabled = false, onClick = { onEvent(Event.OnPurchaseClicked) }, ), modifier = Modifier .fillMaxSize() .padding(horizontal = MainTheme.spacings.quadruple) .verticalScroll(scrollState), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.triple), ) { TextTitleLarge( text = "ContributionScreen", ContributionHeader() ContributionList( contributions = if (state.isRecurringContributionSelected) { state.recurringContributions } else { state.oneTimeContributions }, selectedItem = state.selectedContribution, isRecurringContributionSelected = state.isRecurringContributionSelected, onOneTimeContributionTypeClick = { onEvent(Event.OnOneTimeContributionSelected) }, onRecurringContributionTypeClick = { onEvent(Event.OnRecurringContributionSelected) }, onItemClick = { onEvent(Event.OnContributionItemClicked(it)) }, ) TextBodyMedium(text = "is recurring selected: ${state.isRecurringContributionSelected}") ContributionFooter( onClick = { onEvent(Event.OnPurchaseClicked) }, isPurchaseEnabled = state.selectedContribution != null, ) } } }
feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionContract.kt +3 −3 Original line number Diff line number Diff line Loading @@ -21,9 +21,9 @@ internal class ContributionContract { ) sealed interface Event { data object OnOneTimeContributionClicked : Event data object OnRecurringContributionClicked : Event data class OnContributionClicked(val item: Contribution) : Event data object OnOneTimeContributionSelected : Event data object OnRecurringContributionSelected : Event data class OnContributionItemClicked(val item: Contribution) : Event data object OnPurchaseClicked : Event } }
feature/funding/googleplay/src/main/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionViewModel.kt +33 −6 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import app.k9mail.core.ui.compose.common.mvi.BaseViewModel import app.k9mail.feature.funding.googleplay.domain.entity.Contribution import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.ViewModel Loading @@ -12,14 +13,40 @@ internal class ContributionViewModel( override fun event(event: Event) { when (event) { is Event.OnContributionClicked -> TODO() Event.OnOneTimeContributionSelected -> onOneTimeContributionSelected() Event.OnRecurringContributionSelected -> onRecurringContributionSelected() is Event.OnContributionItemClicked -> onContributionItemClicked(event.item) Event.OnPurchaseClicked -> onPurchaseClicked() } } private fun onOneTimeContributionSelected() { updateState { it.copy( isRecurringContributionSelected = false, selectedContribution = it.oneTimeContributions.firstOrNull(), ) } } Event.OnOneTimeContributionClicked -> TODO() Event.OnPurchaseClicked -> { // TODO private fun onRecurringContributionSelected() { updateState { it.copy( isRecurringContributionSelected = true, selectedContribution = it.recurringContributions.firstOrNull(), ) } } Event.OnRecurringContributionClicked -> TODO() private fun onContributionItemClicked(item: Contribution) { updateState { it.copy( selectedContribution = item, ) } } private fun onPurchaseClicked() { // TODO: Implement purchase logic } }
feature/funding/googleplay/src/test/kotlin/app/k9mail/feature/funding/googleplay/ui/contribution/ContributionViewModelTest.kt 0 → 100644 +118 −0 Original line number Diff line number Diff line package app.k9mail.feature.funding.googleplay.ui.contribution import app.k9mail.core.ui.compose.testing.MainDispatcherRule import app.k9mail.core.ui.compose.testing.mvi.MviContext import app.k9mail.core.ui.compose.testing.mvi.MviTurbines import app.k9mail.core.ui.compose.testing.mvi.runMviTest import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck import app.k9mail.feature.funding.googleplay.domain.entity.Contribution import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.Event import app.k9mail.feature.funding.googleplay.ui.contribution.ContributionContract.State import assertk.assertThat import assertk.assertions.isEqualTo import kotlin.test.Test import org.junit.Rule class ContributionViewModelTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun `should change selected contribution and selected type when on time contribution selected`() = runMviTest { val initialState = State( isRecurringContributionSelected = true, oneTimeContributions = FakeData.oneTimeContributions, ) contributionRobot(initialState) { selectOneTimeContribution() verifyOneTimeContributionSelected() } } @Test fun `should change selected contribution and selected type when recurring contribution selected`() = runMviTest { val initialState = State( isRecurringContributionSelected = false, recurringContributions = FakeData.recurringContributions, ) contributionRobot(initialState) { selectRecurringContribution() verifyRecurringContributionSelected() } } @Test fun `should change selected contribution when contribution item clicked`() = runMviTest { val initialState = State( isRecurringContributionSelected = true, recurringContributions = FakeData.recurringContributions, ) val selectedContribution = FakeData.recurringContributions[2] contributionRobot(initialState) { selectContributionItem(selectedContribution) verifyContributionItemSelected(selectedContribution) } } } private suspend fun MviContext.contributionRobot( initialState: State = State(), interaction: suspend ContributionRobot.() -> Unit, ) = ContributionRobot(this, initialState).apply { initialize() interaction() } private class ContributionRobot( private val mviContext: MviContext, private val initialState: State = State(), ) { private val viewModel = ContributionViewModel(initialState) private lateinit var turbines: MviTurbines<State, Nothing> suspend fun initialize() { turbines = mviContext.turbinesWithInitialStateCheck(viewModel, initialState) } fun selectOneTimeContribution() { viewModel.event(Event.OnOneTimeContributionSelected) } suspend fun verifyOneTimeContributionSelected() { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( isRecurringContributionSelected = false, selectedContribution = initialState.oneTimeContributions.first(), ), ) } fun selectRecurringContribution() { viewModel.event(Event.OnRecurringContributionSelected) } suspend fun verifyRecurringContributionSelected() { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( isRecurringContributionSelected = true, selectedContribution = initialState.recurringContributions.first(), ), ) } fun selectContributionItem(item: Contribution) { viewModel.event(Event.OnContributionItemClicked(item)) } suspend fun verifyContributionItemSelected(item: Contribution) { assertThat(turbines.awaitStateItem()).isEqualTo( initialState.copy( selectedContribution = item, ), ) } }