diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt index a7608ff25d..d9ff4ea2c3 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewActivity.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.SnackbarHostState import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -24,6 +25,7 @@ import com.kickstarter.libs.utils.extensions.isDarkModeEnabled import com.kickstarter.ui.IntentKey import com.kickstarter.ui.SharedPreferenceKey import com.kickstarter.ui.activities.AppThemes +import com.kickstarter.ui.activities.ProfileActivity import com.kickstarter.ui.compose.designsystem.KickstarterApp import com.kickstarter.ui.extensions.startCreatorMessageActivity import com.kickstarter.ui.extensions.transition @@ -46,12 +48,16 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() { ?.getInt(SharedPreferenceKey.APP_THEME, AppThemes.MATCH_SYSTEM.ordinal) ?: AppThemes.MATCH_SYSTEM.ordinal + val ppoUIState by viewModel.ppoUIState.collectAsStateWithLifecycle() + val darkModeEnabled = this.isDarkModeEnabled(env = env) val lazyListState = rememberLazyListState() val snackbarHostState = remember { SnackbarHostState() } val ppoCardPagingSource = viewModel.ppoCardsState.collectAsLazyPagingItems() - val totalAlerts = viewModel.totalAlertsState.collectAsStateWithLifecycle() + val totalAlerts = ppoUIState.totalAlerts + val isLoading = ppoUIState.isLoading || !ppoCardPagingSource.loadState.isIdle + val isErrored = ppoUIState.isErrored || ppoCardPagingSource.loadState.hasError KickstarterApp( useDarkTheme = @@ -72,18 +78,23 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() { lazyColumnListState = lazyListState, errorSnackBarHostState = snackbarHostState, ppoCards = ppoCardPagingSource, - totalAlerts = totalAlerts.value, + totalAlerts = totalAlerts, onAddressConfirmed = { viewModel.showSnackbarAndRefreshCardsList() }, - onCardClick = { }, onProjectPledgeSummaryClick = { url -> openBackingDetailsWebView(url) }, - onSendMessageClick = { projectName -> viewModel.onMessageCreatorClicked(projectName) } + onSendMessageClick = { projectName -> viewModel.onMessageCreatorClicked(projectName) }, + isLoading = isLoading, + isErrored = isErrored, + onSeeAllBackedProjectsClick = { startProfileActivity() }, + pullRefreshCallback = { + // TODO call viewmodel.getPledgedProjects() here + } ) } LaunchedEffect(Unit) { viewModel.projectFlow .collect { - startCreatorMessageActivity(it, previousScreen = MessagePreviousScreenType.PLEDGED_PROJECTS_OVERVIEW) + startCreatorMessageActivity(project = it, previousScreen = MessagePreviousScreenType.PLEDGED_PROJECTS_OVERVIEW) } } @@ -108,4 +119,13 @@ class PledgedProjectsOverviewActivity : AppCompatActivity() { .putExtra(IntentKey.URL, url) startActivity(intent) } + + fun startProfileActivity() { + startActivity( + Intent(this, ProfileActivity::class.java) + ) + this.let { + TransitionUtils.transition(it, TransitionUtils.slideInFromRight()) + } + } } diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt index 0eacff7b3d..4d04e483e0 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreen.kt @@ -1,7 +1,12 @@ package com.kickstarter.features.pledgedprojectsoverview.ui import android.content.res.Configuration +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -11,10 +16,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Scaffold import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,7 +33,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.paging.PagingData import androidx.paging.compose.LazyPagingItems @@ -31,7 +43,10 @@ import androidx.paging.compose.collectAsLazyPagingItems import com.kickstarter.R import com.kickstarter.features.pledgedprojectsoverview.data.PPOCard import com.kickstarter.features.pledgedprojectsoverview.data.PPOCardFactory +import com.kickstarter.libs.utils.extensions.isNullOrZero import com.kickstarter.ui.compose.designsystem.KSAlertDialog +import com.kickstarter.ui.compose.designsystem.KSCircularProgressIndicator +import com.kickstarter.ui.compose.designsystem.KSPrimaryGreenButton import com.kickstarter.ui.compose.designsystem.KSTheme import com.kickstarter.ui.compose.designsystem.KSTheme.colors import com.kickstarter.ui.compose.designsystem.KSTheme.dimensions @@ -58,15 +73,70 @@ private fun PledgedProjectsOverviewScreenPreview() { totalAlerts = 10, onBackPressed = {}, onAddressConfirmed = {}, - onCardClick = {}, onProjectPledgeSummaryClick = {}, onSendMessageClick = {}, + onSeeAllBackedProjectsClick = {}, errorSnackBarHostState = SnackbarHostState() ) } } } +@Composable +@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun PledgedProjectsOverviewScreenErrorPreview() { + KSTheme { + Scaffold( + backgroundColor = colors.backgroundSurfacePrimary + ) { padding -> + val ppoCardList1 = (0..10).map { + PPOCardFactory.confirmAddressCard() + } + val ppoCardPagingList = flowOf(PagingData.from(ppoCardList1)).collectAsLazyPagingItems() + PledgedProjectsOverviewScreen( + modifier = Modifier.padding(padding), + lazyColumnListState = rememberLazyListState(), + ppoCards = ppoCardPagingList, + totalAlerts = 10, + onBackPressed = {}, + onAddressConfirmed = {}, + onProjectPledgeSummaryClick = {}, + onSendMessageClick = {}, + onSeeAllBackedProjectsClick = {}, + isErrored = true, + errorSnackBarHostState = SnackbarHostState() + ) + } + } +} + +@Composable +@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun PledgedProjectsOverviewScreenEmptyPreview() { + KSTheme { + Scaffold( + backgroundColor = colors.backgroundSurfacePrimary + ) { padding -> + val ppoCardPagingList = flowOf(PagingData.from(listOf())).collectAsLazyPagingItems() + PledgedProjectsOverviewScreen( + modifier = Modifier.padding(padding), + lazyColumnListState = rememberLazyListState(), + ppoCards = ppoCardPagingList, + totalAlerts = 0, + onBackPressed = {}, + onAddressConfirmed = {}, + onProjectPledgeSummaryClick = {}, + onSendMessageClick = {}, + onSeeAllBackedProjectsClick = {}, + errorSnackBarHostState = SnackbarHostState() + ) + } + } +} + +@OptIn(ExperimentalMaterialApi::class) @Composable fun PledgedProjectsOverviewScreen( modifier: Modifier, @@ -76,15 +146,23 @@ fun PledgedProjectsOverviewScreen( errorSnackBarHostState: SnackbarHostState, ppoCards: LazyPagingItems, totalAlerts: Int = 0, - onCardClick: () -> Unit, onProjectPledgeSummaryClick: (backingDetailsUrl: String) -> Unit, - onSendMessageClick: (projectName: String) -> Unit + onSendMessageClick: (projectName: String) -> Unit, + onSeeAllBackedProjectsClick: () -> Unit, + isLoading: Boolean = false, + isErrored: Boolean = false, + pullRefreshCallback: () -> Unit = {} ) { val openConfirmAddressAlertDialog = remember { mutableStateOf(false) } var confirmedAddress by remember { mutableStateOf("") } // TODO: This is either the original shipping address or the user-edited address - + val pullRefreshState = rememberPullRefreshState( + isLoading, + pullRefreshCallback, + ) Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState), contentAlignment = Alignment.Center ) { Scaffold( @@ -106,61 +184,67 @@ fun PledgedProjectsOverviewScreen( }, backgroundColor = colors.backgroundSurfacePrimary ) { padding -> - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding( - start = dimensions.paddingMedium, - end = dimensions.paddingMedium, - top = dimensions.paddingMedium - ) - .padding(paddingValues = padding), - state = lazyColumnListState - ) { - item { - Text( - text = stringResource(id = R.string.alerts_fpo, totalAlerts), - style = typography.title3Bold, - color = colors.textPrimary - ) - } + if (isErrored) { + PPOScreenErrorState() + } else if (totalAlerts == 0 || ppoCards.itemCount.isNullOrZero()) { + PPOScreenEmptyState(onSeeAllBackedProjectsClick) + } else { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding( + start = dimensions.paddingMedium, + end = dimensions.paddingMedium, + top = dimensions.paddingMedium + ) + .padding(paddingValues = padding), + state = lazyColumnListState + ) { + item { + Text( + text = stringResource(id = R.string.alerts_fpo, totalAlerts), + style = typography.title3Bold, + color = colors.textPrimary + ) + } - items( - count = ppoCards.itemCount - ) { index -> - Spacer(modifier = Modifier.height(dimensions.paddingMedium)) - - ppoCards[index]?.let { - PPOCardView( - viewType = it.viewType() ?: PPOCardViewType.UNKNOWN, - onCardClick = { }, - onProjectPledgeSummaryClick = { onProjectPledgeSummaryClick(it.backingDetailsUrl() ?: "") }, - projectName = it.projectName(), - pledgeAmount = it.amount(), - imageUrl = it.imageUrl(), - imageContentDescription = it.imageContentDescription(), - creatorName = it.creatorName(), - sendAMessageClickAction = { onSendMessageClick(it.projectSlug() ?: "") }, - shippingAddress = it.address() ?: "", // TODO replace with formatted address from PPO response - showBadge = it.showBadge(), - onActionButtonClicked = { }, - onSecondaryActionButtonClicked = { - when (it.viewType()) { - PPOCardViewType.CONFIRM_ADDRESS -> { - confirmedAddress = it.address() ?: "" - openConfirmAddressAlertDialog.value = true + items( + count = ppoCards.itemCount + ) { index -> + Spacer(modifier = Modifier.height(dimensions.paddingMedium)) + + ppoCards[index]?.let { + PPOCardView( + viewType = it.viewType() ?: PPOCardViewType.UNKNOWN, + onCardClick = { }, + onProjectPledgeSummaryClick = { onProjectPledgeSummaryClick(it.backingDetailsUrl() ?: "") }, + projectName = it.projectName(), + pledgeAmount = it.amount(), + imageUrl = it.imageUrl(), + imageContentDescription = it.imageContentDescription(), + creatorName = it.creatorName(), + sendAMessageClickAction = { onSendMessageClick(it.projectSlug() ?: "") }, + shippingAddress = it.address() ?: "", // TODO replace with formatted address from PPO response + showBadge = it.showBadge(), + onActionButtonClicked = { }, + onSecondaryActionButtonClicked = { + when (it.viewType()) { + PPOCardViewType.CONFIRM_ADDRESS -> { + confirmedAddress = it.address() ?: "" + openConfirmAddressAlertDialog.value = true + } + else -> {} } - else -> {} - } - }, - timeNumberForAction = it.timeNumberForAction() - ) + }, + timeNumberForAction = it.timeNumberForAction() + ) + } } - } - item { - Spacer(modifier = Modifier.height(dimensions.paddingDoubleLarge)) + item { + Spacer(modifier = Modifier.height(dimensions.paddingDoubleLarge)) + } } } } @@ -187,6 +271,91 @@ fun PledgedProjectsOverviewScreen( ) } } + + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .background(KSTheme.colors.backgroundAccentGraySubtle.copy(alpha = 0.5f)) + .clickable(enabled = false) { }, + contentAlignment = Alignment.Center + ) { + KSCircularProgressIndicator() + } + } +} + +@Composable +fun PPOScreenEmptyState( + onSeeAllBackedProjectsClick: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding( + start = dimensions.paddingMedium, + end = dimensions.paddingMedium, + top = dimensions.paddingMedium + ) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text( + color = colors.textPrimary, + text = stringResource(id = R.string.youre_all_caught_up_fpo), + style = typography.title3Bold, + ) + + Spacer(modifier = Modifier.height(dimensions.paddingMediumLarge)) + + Text( + color = colors.textPrimary, + text = stringResource(id = R.string.when_projects_youve_backed_need_your_attention_youll_see_them_here_fpo), + style = typography.body, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(dimensions.paddingMediumLarge)) + + KSPrimaryGreenButton( + modifier = Modifier, + onClickAction = { onSeeAllBackedProjectsClick.invoke() }, + text = stringResource(id = R.string.see_all_backed__projects_fpo), + isEnabled = true + ) + } +} + +@Composable +fun PPOScreenErrorState() { + Column( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding( + start = dimensions.paddingMedium, + end = dimensions.paddingMedium, + top = dimensions.paddingMedium + ) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + + Image( + painter = painterResource(id = R.drawable.ic_refresh_arrow), + contentDescription = null, + ) + Spacer(modifier = Modifier.height(dimensions.paddingMediumLarge)) + + Text( + color = colors.textPrimary, + text = (stringResource(id = R.string.something_went_wrong_pull_to_refresh_fpo)), + style = typography.body, + textAlign = TextAlign.Center + ) + } } enum class PledgedProjectsOverviewScreenTestTag { diff --git a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt index 66510c2d40..47410fc0f6 100644 --- a/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt +++ b/app/src/main/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import com.kickstarter.R import com.kickstarter.features.pledgedprojectsoverview.data.PPOCard +import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOverviewQueryData import com.kickstarter.libs.Environment import com.kickstarter.models.Project import kotlinx.coroutines.flow.MutableSharedFlow @@ -18,21 +19,38 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asFlow +data class PledgedProjectsOverviewUIState( + var totalAlerts: Int = 0, + val isLoading: Boolean = false, + val isErrored: Boolean = false, +) + class PledgedProjectsOverviewViewModel(environment: Environment) : ViewModel() { - private val ppoCards = MutableStateFlow>(PagingData.empty()) - private val totalAlerts = MutableStateFlow(0) + private val mutablePpoCards = MutableStateFlow>(PagingData.empty()) private var mutableProjectFlow = MutableSharedFlow() private var snackbarMessage: (stringID: Int) -> Unit = {} - + private var totalAlerts = 0 private val apolloClient = requireNotNull(environment.apolloClientV2()) - val ppoCardsState: StateFlow> = ppoCards.asStateFlow() - val totalAlertsState: StateFlow = totalAlerts.asStateFlow() + + private val mutablePPOUIState = MutableStateFlow(PledgedProjectsOverviewUIState()) + val ppoCardsState: StateFlow> = mutablePpoCards.asStateFlow() + + val ppoUIState: StateFlow + get() = mutablePPOUIState + .asStateFlow() + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = PledgedProjectsOverviewUIState() + ) fun showSnackbarAndRefreshCardsList() { snackbarMessage.invoke(R.string.address_confirmed_snackbar_text_fpo) @@ -66,12 +84,43 @@ class PledgedProjectsOverviewViewModel(environment: Environment) : ViewModel() { ) .asFlow() .onStart { - // TODO emit loading ui state + emitCurrentState(isLoading = true) }.map { project -> mutableProjectFlow.emit(project) }.catch { snackbarMessage.invoke(R.string.Something_went_wrong_please_try_again) + }.onCompletion { + emitCurrentState() }.collect() } } + + fun getPledgedProjects(inputData: PledgedProjectsOverviewQueryData) { + viewModelScope.launch { + // TODO how we are fetching the data will be modified once the pagination piece in MBL-1473 is finished + apolloClient.getPledgedProjectsOverviewPledges( + inputData = inputData, + ) + .asFlow() + .onStart { + emitCurrentState(isLoading = true) + }.map { ppoEnvelope -> + // update paginated ppo card list here + totalAlerts = ppoEnvelope.totalCount() ?: 0 + emitCurrentState() + }.catch { + emitCurrentState(isErrored = true) + }.collect() + } + } + + private suspend fun emitCurrentState(isLoading: Boolean = false, isErrored: Boolean = false) { + mutablePPOUIState.emit( + PledgedProjectsOverviewUIState( + isLoading = isLoading, + isErrored = isErrored, + totalAlerts = totalAlerts + ) + ) + } } diff --git a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt index 4d58c6cc71..d49b12fc95 100644 --- a/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt +++ b/app/src/main/java/com/kickstarter/services/transformers/GraphQLTransformers.kt @@ -949,6 +949,7 @@ fun pledgedProjectsOverviewEnvelopeTransformer(ppoResponse: PledgedProjectsOverv } return PledgedProjectsOverviewEnvelope.builder() + .totalCount(ppoResponse.pledges()?.totalCount()) .pledges(ppoCards) .categories(categories) .pageInfoEnvelope(pageInfoEnvelope) diff --git a/app/src/main/res/drawable/ic_refresh_arrow.xml b/app/src/main/res/drawable/ic_refresh_arrow.xml new file mode 100644 index 0000000000..36d7c6a21b --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_arrow.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2da0c6ea77..1688a256f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,10 +88,14 @@ This project was successfully funded on %{deadline}, but you can still pledge for available rewards. - + + See all backed projects + You\'re all caught up! + When projects you\'ve backed need your attention, you\'ll see them here. Project Alerts Alerts (%1$s) Address confirmed! Need to change your address before it locks? Visit your backing details on our website. Backing details + Something went wrong - Pull to refresh diff --git a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt index cb04e60336..a389e938d5 100644 --- a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt +++ b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/ui/PledgedProjectsOverviewScreenTest.kt @@ -36,8 +36,8 @@ PledgedProjectsOverviewScreenTest : KSRobolectricTestCase() { errorSnackBarHostState = SnackbarHostState(), onSendMessageClick = {}, onAddressConfirmed = {}, - onCardClick = {}, - onProjectPledgeSummaryClick = {} + onProjectPledgeSummaryClick = {}, + onSeeAllBackedProjectsClick = {} ) } } diff --git a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt index a282aa1429..6562668649 100644 --- a/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/features/pledgedprojectsoverview/viewmodel/PledgedProjectsOverviewViewModelTest.kt @@ -2,6 +2,9 @@ package com.kickstarter.features.pledgedprojectsoverview.viewmodel import com.kickstarter.KSRobolectricTestCase import com.kickstarter.R +import com.kickstarter.features.pledgedprojectsoverview.data.PPOCardFactory +import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOverviewEnvelope +import com.kickstarter.features.pledgedprojectsoverview.data.PledgedProjectsOverviewQueryData import com.kickstarter.mock.factories.ProjectFactory import com.kickstarter.mock.services.MockApolloClientV2 import com.kickstarter.models.Project @@ -85,4 +88,97 @@ class PledgedProjectsOverviewViewModelTest : KSRobolectricTestCase() { R.string.address_confirmed_snackbar_text_fpo ) } + + @Test + fun `emits_error_state_when_errored`() = + runTest { + val mockApolloClientV2 = object : MockApolloClientV2() { + + override fun getPledgedProjectsOverviewPledges(inputData: PledgedProjectsOverviewQueryData): Observable { + return Observable.error(Throwable()) + } + } + + viewModel = PledgedProjectsOverviewViewModel.Factory(environment = environment().toBuilder().apolloClientV2(mockApolloClientV2).build()) + .create(PledgedProjectsOverviewViewModel::class.java) + + val uiState = mutableListOf() + + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + viewModel.ppoUIState.toList(uiState) + } + + viewModel.getPledgedProjects(PledgedProjectsOverviewQueryData(10, null, null, null)) + + assertEquals( + uiState, + listOf( + PledgedProjectsOverviewUIState(isLoading = false, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = true, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = false, isErrored = true, totalAlerts = 0) + ) + ) + } + + @Test + fun `emits_empty_state_when_no_pledges`() = + runTest { + val mockApolloClientV2 = object : MockApolloClientV2() { + + override fun getPledgedProjectsOverviewPledges(inputData: PledgedProjectsOverviewQueryData): Observable { + return Observable.just(PledgedProjectsOverviewEnvelope.builder().totalCount(0).build()) + } + } + + viewModel = PledgedProjectsOverviewViewModel.Factory(environment = environment().toBuilder().apolloClientV2(mockApolloClientV2).build()) + .create(PledgedProjectsOverviewViewModel::class.java) + + val uiState = mutableListOf() + + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + viewModel.ppoUIState.toList(uiState) + } + + viewModel.getPledgedProjects(PledgedProjectsOverviewQueryData(10, null, null, null)) + + assertEquals( + uiState, + listOf( + PledgedProjectsOverviewUIState(isLoading = false, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = true, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = false, isErrored = false, totalAlerts = 0) + ) + ) + } + + @Test + fun `emits_loading_then_success_state_when_successful`() = + runTest { + val mockApolloClientV2 = object : MockApolloClientV2() { + + override fun getPledgedProjectsOverviewPledges(inputData: PledgedProjectsOverviewQueryData): Observable { + return Observable.just(PledgedProjectsOverviewEnvelope.builder().totalCount(10).pledges(listOf(PPOCardFactory.confirmAddressCard())).build()) + } + } + + viewModel = PledgedProjectsOverviewViewModel.Factory(environment = environment().toBuilder().apolloClientV2(mockApolloClientV2).build()) + .create(PledgedProjectsOverviewViewModel::class.java) + + val uiState = mutableListOf() + + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + viewModel.ppoUIState.toList(uiState) + } + + viewModel.getPledgedProjects(PledgedProjectsOverviewQueryData(10, null, null, null)) + + assertEquals( + uiState, + listOf( + PledgedProjectsOverviewUIState(isLoading = false, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = true, isErrored = false, totalAlerts = 0), + PledgedProjectsOverviewUIState(isLoading = false, isErrored = false, totalAlerts = 10) + ) + ) + } }