diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt index 651a2ccea90..a31c5561d74 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreModule.kt @@ -16,6 +16,7 @@ import com.woocommerce.android.datastore.DataStoreType.DASHBOARD_STATS import com.woocommerce.android.datastore.DataStoreType.LAST_UPDATE import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_ADDRESS import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_CONFIGURATION +import com.woocommerce.android.datastore.DataStoreType.SITE_PICKER_WOO_VISIBLE_SITES import com.woocommerce.android.datastore.DataStoreType.TOP_PERFORMER_PRODUCTS import com.woocommerce.android.datastore.DataStoreType.TRACKER import com.woocommerce.android.di.AppCoroutineScope @@ -161,6 +162,24 @@ class DataStoreModule { scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) ) + @Provides + @Singleton + @DataStoreQualifier(SITE_PICKER_WOO_VISIBLE_SITES) + fun provideWooVisibleSitesDataStore( + appContext: Context, + crashLogging: CrashLogging, + @AppCoroutineScope appCoroutineScope: CoroutineScope + ) = PreferenceDataStoreFactory.create( + produceFile = { + appContext.preferencesDataStoreFile("site_picker_visible_sites") + }, + corruptionHandler = ReplaceFileCorruptionHandler { + crashLogging.recordEvent("Corrupted data store. DataStore Type: ${SITE_PICKER_WOO_VISIBLE_SITES.name}") + emptyPreferences() + }, + scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO) + ) + @Provides @Singleton @DataStoreQualifier(SHIPPING_LABEL_CONFIGURATION) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt index 6295e074618..3cacd5bba82 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/datastore/DataStoreType.kt @@ -8,6 +8,7 @@ enum class DataStoreType { TOP_PERFORMER_PRODUCTS, COUPONS, LAST_UPDATE, + SITE_PICKER_WOO_VISIBLE_SITES, SHIPPING_LABEL_CONFIGURATION, SHIPPING_LABEL_ADDRESS } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/AccountRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/AccountRepository.kt index a8c7ccb6f0b..34594e5edee 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/AccountRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/AccountRepository.kt @@ -8,6 +8,7 @@ import com.woocommerce.android.di.AppCoroutineScope import com.woocommerce.android.support.zendesk.ZendeskSettings import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.tools.SiteConnectionType +import com.woocommerce.android.ui.sitepicker.sitevisibility.VisibleWooSitesDataStore import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T.LOGIN import com.woocommerce.android.util.dispatchAndAwait @@ -32,7 +33,8 @@ class AccountRepository @Inject constructor( private val dispatcher: Dispatcher, private val zendeskSettings: ZendeskSettings, private val prefs: AppPrefs, - @AppCoroutineScope private val appCoroutineScope: CoroutineScope + @AppCoroutineScope private val appCoroutineScope: CoroutineScope, + private val siteVisibilityDataStore: VisibleWooSitesDataStore ) { fun getUserAccount(): AccountModel? = accountStore.account.takeIf { it.userId != 0L } @@ -112,8 +114,11 @@ class AccountRepository @Inject constructor( AnalyticsTracker.clearAllData() zendeskSettings.clearIdentity() - // Wipe user-specific preferences - prefs.resetUserPreferences() + // Wipe user-specific preferences and prefs data store + appCoroutineScope.launch { + prefs.resetUserPreferences() + siteVisibilityDataStore.clearAll() + } selectedSite.reset() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerAdapter.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerAdapter.kt index 6144c013795..fd77adf7394 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerAdapter.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerAdapter.kt @@ -84,15 +84,15 @@ class SitePickerAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (val item = getItem(position)) { - is Header -> (holder as HeaderViewHolder).bind(item.label) + is Header -> (holder as HeaderViewHolder).bind(item.label, item.numberHiddenSites) is WooSiteUiModel -> (holder as WooSiteViewHolder).bind(item) is NonWooSiteUiModel -> (holder as NonWooSiteViewHolder).bind(item) } } private class HeaderViewHolder(val view: MaterialTextView) : RecyclerView.ViewHolder(view) { - fun bind(@StringRes label: Int) { - view.setText(label) + fun bind(@StringRes label: Int, numHiddenSites: Int) { + view.text = view.resources.getString(label, numHiddenSites) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt index 46fa2528e1f..f2b26d1dc62 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerFragment.kt @@ -49,6 +49,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState.StoreListState import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState.WooNotFoundState import com.woocommerce.android.ui.sitepicker.sitediscovery.SitePickerSiteDiscoveryFragment +import com.woocommerce.android.ui.sitepicker.sitevisibility.WooSitesVisibilityFragment import com.woocommerce.android.util.ChromeCustomTabUtils import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Logout @@ -250,6 +251,9 @@ class SitePickerFragment : handleNotice(AccountMismatchErrorFragment.JETPACK_CONNECTED_NOTICE) { viewModel.onJetpackConnected() } + handleResult(WooSitesVisibilityFragment.WOO_SITES_VISIBILITY_UPDATED) { + viewModel.onWooSitesVisibilityUpdated() + } } private fun updateStoreListView() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt index 61478d26b62..0dc59d01d98 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModel.kt @@ -27,6 +27,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerEvent import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.Header import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.NonWooSiteUiModel import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.WooSiteUiModel +import com.woocommerce.android.ui.sitepicker.sitevisibility.GetWooVisibleSites import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.util.WooLog import com.woocommerce.android.viewmodel.LiveDataDelegate @@ -64,7 +65,8 @@ class SitePickerViewModel @Inject constructor( private val unifiedLoginTracker: UnifiedLoginTracker, private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val userEligibilityFetcher: UserEligibilityFetcher, - private val experimentTracker: ExperimentTracker + private val experimentTracker: ExperimentTracker, + private val getWooVisibleSites: GetWooVisibleSites ) : ScopedViewModel(savedState) { companion object { private const val WOOCOMMERCE_INSTALLATION_URL = "https://wordpress.com/plugins/woocommerce/" @@ -198,7 +200,7 @@ class SitePickerViewModel @Inject constructor( ) } - private fun onSitesLoaded(sites: List) { + private suspend fun onSitesLoaded(sites: List) { if (sites.isEmpty()) { when { loginSiteAddress != null -> showAccountMismatchScreen(loginSiteAddress!!) @@ -222,23 +224,8 @@ class SitePickerViewModel @Inject constructor( ) } val selectedSiteId = selectedSiteId.value ?: wooSites.getOrNull(0)?.id - _sites.value = buildList { - if (wooSites.isNotEmpty()) { - add(Header(R.string.login_pick_store)) - addAll( - wooSites.map { - WooSiteUiModel( - site = it, - isSelected = selectedSiteId == it.id - ) - } - ) - } - if (navArgs.openedFromLogin && nonWooSites.isNotEmpty()) { - add(Header(R.string.login_non_woo_stores_label)) - addAll(nonWooSites.map { NonWooSiteUiModel(it) }) - } - } + _sites.value = buildSitesList(wooSites, selectedSiteId, nonWooSites) + sitePickerViewState = sitePickerViewState.copy( hasConnectedStores = sites.isNotEmpty(), isPrimaryBtnVisible = wooSites.isNotEmpty(), @@ -256,6 +243,34 @@ class SitePickerViewModel @Inject constructor( } } + private suspend fun buildSitesList( + wooSites: List, + selectedSiteId: Int?, + nonWooSites: List + ): List = buildList { + if (wooSites.isNotEmpty()) { + val wooVisibleSites = getWooVisibleSites() + val numberOfHiddenSites = wooSites.size - wooVisibleSites.size + val string = when (numberOfHiddenSites) { + 0 -> string.login_pick_store + else -> string.site_picker_select_store_list_header_with_hidden_sites + } + add(Header(string, numberOfHiddenSites)) + addAll( + wooVisibleSites.map { + WooSiteUiModel( + site = it, + isSelected = selectedSiteId == it.id + ) + } + ) + } + if (navArgs.openedFromLogin && nonWooSites.isNotEmpty()) { + add(Header(string.login_non_woo_stores_label)) + addAll(nonWooSites.map { NonWooSiteUiModel(it) }) + } + } + /** * Signin M1: User logged in with a URL. Here we check that login url to see * if the site is (in this order): @@ -650,6 +665,12 @@ class SitePickerViewModel @Inject constructor( } } + fun onWooSitesVisibilityUpdated() { + launch { + onSitesLoaded(repository.getSites()) + } + } + @Parcelize data class SitePickerViewState( val userInfo: UserInfo? = null, @@ -677,7 +698,7 @@ class SitePickerViewModel @Inject constructor( sealed interface SitesListItem : Parcelable { @Parcelize - data class Header(@StringRes val label: Int) : SitesListItem + data class Header(@StringRes val label: Int, val numberHiddenSites: Int = 0) : SitesListItem @Parcelize data class WooSiteUiModel( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/GetWooVisibleSites.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/GetWooVisibleSites.kt new file mode 100644 index 00000000000..611b35d8f50 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/GetWooVisibleSites.kt @@ -0,0 +1,19 @@ +package com.woocommerce.android.ui.sitepicker.sitevisibility + +import com.woocommerce.android.ui.sitepicker.SitePickerRepository +import kotlinx.coroutines.flow.first +import org.wordpress.android.fluxc.model.SiteModel +import javax.inject.Inject + +class GetWooVisibleSites @Inject constructor( + private val sitePickerRepository: SitePickerRepository, + private val visibleSitesDataStore: VisibleWooSitesDataStore +) { + suspend operator fun invoke(): List = + sitePickerRepository.getSites() + .filter { it.hasWooCommerce && isSiteVisible(it.siteId) } + + private suspend fun isSiteVisible(siteId: Long): Boolean { + return visibleSitesDataStore.isSiteVisible(siteId).first() + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/VisibleWooSitesDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/VisibleWooSitesDataStore.kt new file mode 100644 index 00000000000..a118eebe14a --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/VisibleWooSitesDataStore.kt @@ -0,0 +1,35 @@ +package com.woocommerce.android.ui.sitepicker.sitevisibility + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import com.woocommerce.android.datastore.DataStoreQualifier +import com.woocommerce.android.datastore.DataStoreType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class VisibleWooSitesDataStore @Inject constructor( + @DataStoreQualifier(DataStoreType.SITE_PICKER_WOO_VISIBLE_SITES) private val dataStore: DataStore +) { + suspend fun updateSiteVisibilityStatus(siteIds: Map) { + siteIds.forEach { (siteId, isVisible) -> + updateSiteVisibility(siteId, isVisible) + } + } + + fun isSiteVisible(siteId: Long): Flow { + return dataStore.data.map { prefs -> prefs[booleanPreferencesKey(siteId.toString())] != false } + } + + suspend fun clearAll() { + dataStore.edit { it.clear() } + } + + private suspend fun updateSiteVisibility(siteId: Long, isVisible: Boolean) { + dataStore.edit { preferences -> + preferences[booleanPreferencesKey(siteId.toString())] = isVisible + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityFragment.kt index e1aa9389ac0..b7194506f54 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityFragment.kt @@ -6,14 +6,20 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.navigateBackWithResult import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class WooSitesVisibilityFragment : BaseFragment() { + companion object { + const val WOO_SITES_VISIBILITY_UPDATED = "woo_sites_visibility_updated" + } + override val activityAppBarStatus: AppBarStatus get() = AppBarStatus.Hidden @@ -33,6 +39,7 @@ class WooSitesVisibilityFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is ExitWithResult<*> -> navigateBackWithResult(WOO_SITES_VISIBILITY_UPDATED, event.data) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/StoreVisibilityScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityScreen.kt similarity index 97% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/StoreVisibilityScreen.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityScreen.kt index 2d204568b15..796b85d8c32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/StoreVisibilityScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityScreen.kt @@ -47,7 +47,7 @@ fun WooSitesVisibilityScreen(viewModel: WooSitesVisibilityViewModel) { state = state, onBack = viewModel::onBackPressed, onSaveTapped = viewModel::onSaveTapped, - onSiteSelected = viewModel::onSiteSelected, + onSiteTapped = viewModel::onSiteTapped, modifier = Modifier.fillMaxWidth() ) } @@ -58,7 +58,7 @@ fun WooSitesVisibilityScreen( state: WooStoresUiState, onBack: () -> Unit, onSaveTapped: () -> Unit, - onSiteSelected: (WooStoreUi) -> Unit, + onSiteTapped: (WooStoreUi) -> Unit, modifier: Modifier = Modifier ) { Scaffold(topBar = { @@ -133,7 +133,7 @@ fun WooSitesVisibilityScreen( ) AvailableStoresForHiding( state = state, - onSiteSelected = onSiteSelected, + onSiteSelected = onSiteTapped, modifier = Modifier .padding(bottom = 16.dp) .border( @@ -237,6 +237,6 @@ fun StoreVisibilityScreenPreview() { ), onBack = {}, onSaveTapped = {}, - onSiteSelected = {} + onSiteTapped = {} ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModel.kt index 8b438066a7d..8ce74711a87 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModel.kt @@ -5,9 +5,11 @@ import androidx.lifecycle.asLiveData import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.sitepicker.SitePickerRepository import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.wordpress.android.fluxc.model.SiteModel import javax.inject.Inject @@ -16,13 +18,14 @@ import javax.inject.Inject class WooSitesVisibilityViewModel @Inject constructor( private val sitePickerRepository: SitePickerRepository, private val selectedSite: SelectedSite, + private val visibleSitesDataStore: VisibleWooSitesDataStore, savedStateHandle: SavedStateHandle ) : ScopedViewModel(savedStateHandle) { private var initiallySelectedSiteIds: List = emptyList() private val _wooStores = MutableStateFlow( WooStoresUiState( wooStores = emptyList(), - currentSite = selectedSite.get().toWooStoreUi(), + currentSite = selectedSite.get().toWooStoreUi(isSiteVisible = false), isSaveButtonEnabled = false ) ) @@ -33,7 +36,7 @@ class WooSitesVisibilityViewModel @Inject constructor( _wooStores.value = _wooStores.value.copy( wooStores = sitePickerRepository.getSites() .filter { it.hasWooCommerce && it.siteId != selectedSite.get().siteId } - .map { it.toWooStoreUi() } + .map { it.toWooStoreUi(isSiteVisible(it.siteId)) } ) initiallySelectedSiteIds = _wooStores.value.wooStores .filter { it.isSelected } @@ -46,10 +49,16 @@ class WooSitesVisibilityViewModel @Inject constructor( } fun onSaveTapped() { - TODO("Not yet implemented") + launch { + visibleSitesDataStore.updateSiteVisibilityStatus( + _wooStores.value.wooStores + .associate { it.siteId to it.isSelected } + ) + triggerEvent(ExitWithResult(data = true)) + } } - fun onSiteSelected(wooStoreUi: WooStoreUi) { + fun onSiteTapped(wooStoreUi: WooStoreUi) { _wooStores.value = _wooStores.value.copy( wooStores = _wooStores.value.wooStores.map { when { @@ -65,11 +74,14 @@ class WooSitesVisibilityViewModel @Inject constructor( ) } - private fun SiteModel.toWooStoreUi() = WooStoreUi( + private suspend fun isSiteVisible(siteId: Long): Boolean = + visibleSitesDataStore.isSiteVisible(siteId).first() + + private fun SiteModel.toWooStoreUi(isSiteVisible: Boolean) = WooStoreUi( siteName = name, siteUrl = url, siteId = siteId, - isSelected = true // TODO remove hardcoded value + isSelected = isSiteVisible ) data class WooStoresUiState( diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 046f16ee329..a12fa25078c 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -365,6 +365,7 @@ + Select store to connect (%d hidden) Enter a site address Connect another store Create a new store diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt index 8ccc376e565..80fdf355b0f 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/SitePickerViewModelTest.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitePickerState import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.NonWooSiteUiModel import com.woocommerce.android.ui.sitepicker.SitePickerViewModel.SitesListItem.WooSiteUiModel +import com.woocommerce.android.ui.sitepicker.sitevisibility.GetWooVisibleSites import com.woocommerce.android.util.captureValues import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest @@ -80,6 +81,9 @@ class SitePickerViewModelTest : BaseUnitTest() { private val accountRepository: AccountRepository = mock() private val unifiedLoginTracker: UnifiedLoginTracker = mock() private val experimentTracker: ExperimentTracker = mock() + private val getWooVisibleSites: GetWooVisibleSites = mock { + onBlocking { invoke() } doReturn defaultExpectedSiteList + } private lateinit var viewModel: SitePickerViewModel private lateinit var savedState: SavedStateHandle @@ -96,6 +100,7 @@ class SitePickerViewModelTest : BaseUnitTest() { userEligibilityFetcher = userEligibilityFetcher, unifiedLoginTracker = unifiedLoginTracker, experimentTracker = experimentTracker, + getWooVisibleSites = getWooVisibleSites ) } @@ -260,6 +265,7 @@ class SitePickerViewModelTest : BaseUnitTest() { if (index < 2) siteModel.apply { hasWooCommerce = false } else siteModel } whenever(repository.fetchWooCommerceSites()).thenReturn(WooResult(expectedSites)) + whenever(getWooVisibleSites.invoke()).thenReturn(expectedSites.filter { it.hasWooCommerce }) whenViewModelIsCreated() val items = viewModel.sites.captureValues().last().toMutableList() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModelTest.kt index 8282f4fcccd..21e8b00ea37 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/sitepicker/sitevisibility/WooSitesVisibilityViewModelTest.kt @@ -5,24 +5,41 @@ import com.woocommerce.android.ui.sitepicker.SitePickerRepository import com.woocommerce.android.ui.sitepicker.SitePickerTestUtils import com.woocommerce.android.ui.sitepicker.sitevisibility.WooSitesVisibilityViewModel.WooStoreUi import com.woocommerce.android.util.getOrAwaitValue +import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.verify @OptIn(ExperimentalCoroutinesApi::class) class WooSitesVisibilityViewModelTest : BaseUnitTest() { companion object { - private val DEFAULT_STORES = SitePickerTestUtils.generateStores().map { + private val ALL_WOO_SITES = SitePickerTestUtils.generateStores().map { it.apply { hasWooCommerce = true name = "name $siteId" url = "www.$siteId.com" } } - private val WOO_STORE_DEFAULT_UI = DEFAULT_STORES.last().let { + private val CURRENT_SELECTED_SITE = ALL_WOO_SITES.first() + private val AVAILABLE_WOO_SITES_TO_HIDE = ALL_WOO_SITES + .filter { it.siteId != CURRENT_SELECTED_SITE.siteId } + .map { + WooStoreUi( + siteName = it.name, + siteUrl = it.url, + siteId = it.siteId, + isSelected = true + ) + } + private val A_WOO_SITE_UI_MODEL = ALL_WOO_SITES.last().let { WooStoreUi( siteName = it.name, siteUrl = it.url, @@ -33,11 +50,15 @@ class WooSitesVisibilityViewModelTest : BaseUnitTest() { } private val sitePickerRepository: SitePickerRepository = mock { - onBlocking { getSites() } doReturn DEFAULT_STORES + onBlocking { getSites() } doReturn ALL_WOO_SITES } private val selectedSite: SelectedSite = mock { - on { get() }.thenReturn(DEFAULT_STORES.first()) + on { get() }.thenReturn(CURRENT_SELECTED_SITE) + } + private val visibleWooSitesDataStore: VisibleWooSitesDataStore = mock { + onBlocking { isSiteVisible(any()) } doReturn flowOf(true) } + private lateinit var viewModel: WooSitesVisibilityViewModel @Before @@ -45,6 +66,7 @@ class WooSitesVisibilityViewModelTest : BaseUnitTest() { viewModel = WooSitesVisibilityViewModel( sitePickerRepository = sitePickerRepository, selectedSite = selectedSite, + visibleSitesDataStore = visibleWooSitesDataStore, savedStateHandle = mock() ) } @@ -52,7 +74,7 @@ class WooSitesVisibilityViewModelTest : BaseUnitTest() { @Test fun `given all sites are selected, when selected sites change, then enable save button`() = testBlocking { - viewModel.onSiteSelected(WOO_STORE_DEFAULT_UI) + viewModel.onSiteTapped(A_WOO_SITE_UI_MODEL) val updatedState = viewModel.viewState.getOrAwaitValue() assert(!updatedState.wooStores.last().isSelected) @@ -62,11 +84,34 @@ class WooSitesVisibilityViewModelTest : BaseUnitTest() { @Test fun `given all sites are selected, when selecting unselecting same site, then save button is disabled`() = testBlocking { - viewModel.onSiteSelected(WOO_STORE_DEFAULT_UI) - viewModel.onSiteSelected(WOO_STORE_DEFAULT_UI) + viewModel.onSiteTapped(A_WOO_SITE_UI_MODEL) + viewModel.onSiteTapped(A_WOO_SITE_UI_MODEL) val updatedState = viewModel.viewState.getOrAwaitValue() - assert(updatedState.wooStores?.first()?.isSelected == true) + assert(updatedState.wooStores.first().isSelected) assert(!updatedState.isSaveButtonEnabled) } + + @Test + fun `given one site is unselected, when tapping save, then save site visibility status`() = + testBlocking { + val hiddenSite = A_WOO_SITE_UI_MODEL + viewModel.onSiteTapped(hiddenSite) + + viewModel.onSaveTapped() + + verify(visibleWooSitesDataStore).updateSiteVisibilityStatus( + AVAILABLE_WOO_SITES_TO_HIDE.associate { it.siteId to (hiddenSite.siteId != it.siteId) } + ) + } + + @Test + fun `when tapping save, then exit with result`() = + testBlocking { + val event = viewModel.event.runAndCaptureValues { + viewModel.onSaveTapped() + }.last() + + assertThat(event).isEqualTo(ExitWithResult(data = true)) + } }