Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit bea5fcfa authored by Wolf-Martell Montwé's avatar Wolf-Martell Montwé
Browse files

Add ContributionContent and ContributionViewModel

parent 3289bcdf
Loading
Loading
Loading
Loading
+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(),
        )
    }
}
+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

@@ -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,
            )
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -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
    }
}
+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
@@ -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
    }
}
+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,
            ),
        )
    }
}