Skip to content

Commit 191cff0

Browse files
authored
CMM-814 hide or show taxonomies in the menu depending on the server configuration (#22255)
* Updating library * Porting the terms fetching * Porting create * Porting delete term * porting update term * Update term fix * Creating the new isHierarchical cocal field * Parent fix * Fixing tests * detekt * Minor fix * Fixing tests * Adding the taxonomies menu view model * Adding show mechanism * Showing taxoniomies * Adding a LiveData * Call categories and tags screens * detekt and style * Adding tests
1 parent 7ac93e0 commit 191cff0

File tree

6 files changed

+290
-12
lines changed

6 files changed

+290
-12
lines changed

WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.wordpress.android.ui.prefs.accountsettings.AccountSettingsViewModel;
5050
import org.wordpress.android.ui.prefs.homepage.HomepageSettingsViewModel;
5151
import org.wordpress.android.ui.prefs.language.LocalePickerViewModel;
52+
import org.wordpress.android.ui.prefs.taxonomies.TaxonomiesNavMenuViewModel;
5253
import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneViewModel;
5354
import org.wordpress.android.ui.publicize.PublicizeListViewModel;
5455
import org.wordpress.android.ui.reader.ReaderCommentListViewModel;
@@ -495,6 +496,12 @@ abstract class ViewModelModule {
495496
@ViewModelKey(BloggingRemindersViewModel.class)
496497
abstract ViewModel bloggingRemindersViewModel(BloggingRemindersViewModel viewModel);
497498

499+
@Binds
500+
@IntoMap
501+
@ViewModelKey(TaxonomiesNavMenuViewModel.class)
502+
abstract ViewModel taxonomiesNavMenuViewModel(TaxonomiesNavMenuViewModel viewModel);
503+
504+
498505
@Binds
499506
@IntoMap
500507
@ViewModelKey(LocalePickerViewModel.class)

WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.wordpress.android.ui.prefs.EditTextPreferenceWithValidation.ValidationType;
8585
import org.wordpress.android.ui.prefs.SiteSettingsFormatDialog.FormatType;
8686
import org.wordpress.android.ui.prefs.homepage.HomepageSettingsDialog;
87+
import org.wordpress.android.ui.prefs.taxonomies.TaxonomiesNavMenuViewModel;
8788
import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneBottomSheet;
8889
import org.wordpress.android.ui.utils.UiHelpers;
8990
import org.wordpress.android.util.AppLog;
@@ -114,6 +115,7 @@
114115
import javax.inject.Inject;
115116

116117
import kotlin.Triple;
118+
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext;
117119

118120
import static org.wordpress.android.ui.prefs.WPComSiteSettings.supportsJetpackSiteAcceleratorSettings;
119121

@@ -194,6 +196,8 @@ public class SiteSettingsFragment extends PreferenceFragment
194196

195197
private BloggingRemindersViewModel mBloggingRemindersViewModel;
196198

199+
private TaxonomiesNavMenuViewModel mTaxonomiesNavMenuViewModel;
200+
197201
public SiteModel mSite;
198202

199203
// Can interface with WP.com or WP.org
@@ -1107,6 +1111,52 @@ public void initPreferences() {
11071111

11081112
initBloggingSection();
11091113
removeEmptyCategories();
1114+
initTaxonomies();
1115+
}
1116+
1117+
private void initTaxonomies() {
1118+
mTaxonomiesNavMenuViewModel = new ViewModelProvider(getAppCompatActivity(), mViewModelFactory)
1119+
.get(TaxonomiesNavMenuViewModel.class);
1120+
mTaxonomiesNavMenuViewModel.getTaxonomies().observe(getAppCompatActivity(), this::showTaxonomies);
1121+
mTaxonomiesNavMenuViewModel.fetchTaxonomies(mSite);
1122+
}
1123+
1124+
private void showTaxonomies(List<TaxonomyTypeDetailsWithEditContext> taxonomies) {
1125+
if (taxonomies.isEmpty()) {
1126+
return;
1127+
}
1128+
PreferenceGroup siteScreen = (PreferenceGroup) findPreference(getString(R.string.pref_key_site_screen));
1129+
if (siteScreen != null) {
1130+
// Create taxonomies preference group
1131+
final String taxonomiesPrefKey = getString(R.string.pref_key_taxonomies);
1132+
PreferenceGroup taxonomiesPreference = (PreferenceGroup) findPreference(taxonomiesPrefKey);
1133+
if (taxonomiesPreference != null) {
1134+
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_taxonomies);
1135+
}
1136+
taxonomiesPreference = new PreferenceCategory(getActivity());
1137+
taxonomiesPreference.setTitle(getString(R.string.taxonomies_title));
1138+
taxonomiesPreference.setKey(taxonomiesPrefKey);
1139+
siteScreen.addPreference(taxonomiesPreference);
1140+
1141+
for (TaxonomyTypeDetailsWithEditContext taxonomy : taxonomies) {
1142+
Preference pref = new Preference(getActivity());
1143+
pref.setTitle(taxonomy.getName());
1144+
pref.setKey(taxonomy.getSlug());
1145+
pref.setOnPreferenceClickListener(preference -> {
1146+
// TODO: Create generic taxonomies DataView and call it from here
1147+
// We are not accepting the taxonomy name as a parameter yet
1148+
// So Categories and Tags are still hardcoded
1149+
if ("category".equals(taxonomy.getSlug())) {
1150+
ActivityLauncher.showCategoriesList(getActivity(), mSite);
1151+
} else if ("post_tag".equals(taxonomy.getSlug())) {
1152+
SiteSettingsTagListActivity.showTagList(getActivity(), mSite);
1153+
}
1154+
return false;
1155+
}
1156+
);
1157+
taxonomiesPreference.addPreference(pref);
1158+
}
1159+
}
11101160
}
11111161

11121162
private void updateHomepageSummary() {
@@ -2033,18 +2083,7 @@ private void removeNonSelfHostedPreferences() {
20332083
if (group != null) {
20342084
group.removeAll();
20352085
}
2036-
if (mSite.isUsingSelfHostedRestApi()) {
2037-
// Remove everything inside "Writing" preference but "Categories" and "Tags" which are now supported
2038-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_category);
2039-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_format);
2040-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_date_format);
2041-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_time_format);
2042-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_week_start);
2043-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_posts_per_page);
2044-
WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_related_posts);
2045-
} else {
2046-
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_writing);
2047-
}
2086+
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_writing);
20482087
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_discussion);
20492088
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_advanced);
20502089
WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_quota);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.wordpress.android.ui.prefs.taxonomies
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
5+
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.viewModelScope
7+
import kotlinx.coroutines.launch
8+
import org.wordpress.android.fluxc.model.SiteModel
9+
import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider
10+
import org.wordpress.android.fluxc.utils.AppLogWrapper
11+
import org.wordpress.android.util.AppLog
12+
import rs.wordpress.api.kotlin.WpRequestResult
13+
import uniffi.wp_api.TaxonomyListParams
14+
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext
15+
import javax.inject.Inject
16+
17+
class TaxonomiesNavMenuViewModel @Inject constructor(
18+
private val wpApiClientProvider: WpApiClientProvider,
19+
private val appLogWrapper: AppLogWrapper,
20+
) : ViewModel() {
21+
// LiveData because this is observed from Java
22+
private val _taxonomies = MutableLiveData<List<TaxonomyTypeDetailsWithEditContext>>()
23+
val taxonomies: LiveData<List<TaxonomyTypeDetailsWithEditContext>> = _taxonomies
24+
25+
fun fetchTaxonomies(site: SiteModel) {
26+
if (!site.isUsingSelfHostedRestApi) {
27+
appLogWrapper.d(
28+
AppLog.T.API,
29+
"Taxonomies - Taxonomies cannot be fetched: Application Password not available"
30+
)
31+
return
32+
}
33+
viewModelScope.launch {
34+
val client = wpApiClientProvider.getWpApiClient(site)
35+
val response = client.request { requestBuilder ->
36+
requestBuilder.taxonomies().listWithEditContext(TaxonomyListParams())
37+
}
38+
when (response) {
39+
is WpRequestResult.Success -> {
40+
val list = response.response.data
41+
appLogWrapper.d(AppLog.T.API, "Taxonomies - Fetched taxonomies ${list.taxonomyTypes.size}")
42+
val taxonomies = mutableListOf<TaxonomyTypeDetailsWithEditContext>()
43+
list.taxonomyTypes.forEach { type ->
44+
appLogWrapper.d(AppLog.T.API, "Taxonomies - Taxonomy ${type.value.name}")
45+
if (type.value.visibility.showInNavMenus) {
46+
taxonomies.add(type.value)
47+
}
48+
}
49+
_taxonomies.value = taxonomies
50+
}
51+
52+
else -> {
53+
appLogWrapper.e(AppLog.T.API, "Taxonomies - Error fetching taxonomies")
54+
}
55+
}
56+
}
57+
}
58+
}

WordPress/src/main/res/values/key_strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<string name="pref_key_language" translatable="false">wp_pref_language</string>
1818
<string name="pref_key_app_theme" translatable="false">wp_pref_app_theme</string>
1919
<string name="pref_key_whats_new" translatable="false">wp_pref_whats_new</string>
20+
<string name="pref_key_taxonomies" translatable="false">wp_pref_taxonomies</string>
2021
<string name="pref_notification_blogs" translatable="false">wp_pref_notification_blogs</string>
2122
<string name="pref_notification_blogs_followed" translatable="false">pref_notification_blogs_followed</string>
2223
<string name="pref_notification_other_category" translatable="false">wp_pref_notification_other_category</string>

WordPress/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,8 @@
590590
<string name="top_level_category_name">Top level</string>
591591
<string name="dlg_confirm_delete_category">Permanently delete \'%s\' Category?</string>
592592

593+
<string name="taxonomies_title">Taxonomies</string>
594+
593595

594596
<!-- action from share intents -->
595597
<string name="share_action_post">Add to new post</string>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package org.wordpress.android.ui.prefs.taxonomies
2+
3+
import kotlinx.coroutines.ExperimentalCoroutinesApi
4+
import kotlinx.coroutines.test.advanceUntilIdle
5+
import kotlinx.coroutines.test.runTest
6+
import org.junit.Assert.assertEquals
7+
import org.junit.Assert.assertNotNull
8+
import org.junit.Assert.assertTrue
9+
import org.junit.Before
10+
import org.junit.Test
11+
import org.mockito.Mock
12+
import org.mockito.MockitoAnnotations
13+
import org.mockito.kotlin.any
14+
import org.mockito.kotlin.mock
15+
import org.mockito.kotlin.never
16+
import org.mockito.kotlin.verify
17+
import org.mockito.kotlin.whenever
18+
import org.wordpress.android.BaseUnitTest
19+
import org.wordpress.android.fluxc.model.SiteModel
20+
import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider
21+
import org.wordpress.android.fluxc.utils.AppLogWrapper
22+
import org.wordpress.android.util.AppLog
23+
import rs.wordpress.api.kotlin.WpApiClient
24+
import rs.wordpress.api.kotlin.WpRequestResult
25+
import uniffi.wp_api.TaxonomiesRequestListWithEditContextResponse
26+
import uniffi.wp_api.TaxonomyType
27+
import uniffi.wp_api.TaxonomyTypeDetailsWithEditContext
28+
import uniffi.wp_api.TaxonomyTypesResponseWithEditContext
29+
import uniffi.wp_api.TaxonomyTypeVisibility
30+
import uniffi.wp_api.WpNetworkHeaderMap
31+
32+
@ExperimentalCoroutinesApi
33+
class TaxonomiesNavMenuViewModelTest : BaseUnitTest() {
34+
@Mock
35+
private lateinit var wpApiClientProvider: WpApiClientProvider
36+
37+
@Mock
38+
private lateinit var wpApiClient: WpApiClient
39+
40+
@Mock
41+
private lateinit var appLogWrapper: AppLogWrapper
42+
43+
private lateinit var viewModel: TaxonomiesNavMenuViewModel
44+
45+
private var taxonomies: List<TaxonomyTypeDetailsWithEditContext> = listOf()
46+
47+
private val testSite = SiteModel().apply {
48+
id = 123
49+
url = "https://test.wordpress.com"
50+
apiRestUsernamePlain = "user"
51+
apiRestPasswordPlain = "pass"
52+
setIsWPCom(false)
53+
}
54+
55+
@Before
56+
fun setUp() {
57+
MockitoAnnotations.openMocks(this)
58+
59+
whenever(wpApiClientProvider.getWpApiClient(testSite)).thenReturn(wpApiClient)
60+
61+
viewModel = TaxonomiesNavMenuViewModel(
62+
wpApiClientProvider,
63+
appLogWrapper
64+
)
65+
viewModel.taxonomies.observeForever { taxonomies = it }
66+
}
67+
68+
@Test
69+
fun `when site does not support self-hosted rest api, then taxonomies are not fetched`() = test {
70+
testSite.setIsWPCom(true)
71+
72+
viewModel.fetchTaxonomies(testSite)
73+
advanceUntilIdle()
74+
75+
verify(wpApiClientProvider, never()).getWpApiClient(any(), any())
76+
verify(appLogWrapper).d(
77+
AppLog.T.API,
78+
"Taxonomies - Taxonomies cannot be fetched: Application Password not available"
79+
)
80+
assertTrue(taxonomies.isEmpty())
81+
}
82+
83+
@Test
84+
fun `when LiveData is observed, it starts with null value`() {
85+
assertNotNull(viewModel.taxonomies)
86+
assertEquals(null, viewModel.taxonomies.value)
87+
}
88+
89+
@Test
90+
fun `fetch taxonomies with success response dispatches success action`() = runTest {
91+
// Create the correct response structure following the MediaRSApiRestClientTest pattern
92+
val response = TaxonomiesRequestListWithEditContextResponse(
93+
createTestTaxonomyTypesResponseWithEditContext(),
94+
mock<WpNetworkHeaderMap>(),
95+
)
96+
97+
val successResponse: WpRequestResult<TaxonomiesRequestListWithEditContextResponse> = WpRequestResult.Success(
98+
response = response
99+
)
100+
101+
whenever(wpApiClient.request<TaxonomiesRequestListWithEditContextResponse>(any())).thenReturn(successResponse)
102+
103+
viewModel.fetchTaxonomies(testSite)
104+
advanceUntilIdle()
105+
106+
val responseList: List<TaxonomyTypeDetailsWithEditContext> = response.data.taxonomyTypes.map { it.value }
107+
assertEquals(responseList, taxonomies)
108+
}
109+
110+
@Test
111+
fun `fetch taxonomies with error response do nothing`() = runTest {
112+
// Use a concrete error type that we can create - UnknownError requires statusCode and response
113+
val errorResponse = WpRequestResult.UnknownError<Any>(
114+
statusCode = 500u,
115+
response = "Internal Server Error"
116+
)
117+
118+
whenever(wpApiClient.request<Any>(any())).thenReturn(errorResponse)
119+
120+
viewModel.fetchTaxonomies(testSite)
121+
122+
verify(appLogWrapper).e(any(), any())
123+
assertTrue(taxonomies.isEmpty())
124+
}
125+
126+
private fun createTestTaxonomyTypesResponseWithEditContext(): TaxonomyTypesResponseWithEditContext {
127+
val visibility = TaxonomyTypeVisibility(
128+
public = true,
129+
publiclyQueryable = true,
130+
showUi = true,
131+
showAdminColumn = true,
132+
showInNavMenus = true,
133+
showInQuickEdit = true
134+
)
135+
136+
val categoryDetails = TaxonomyTypeDetailsWithEditContext(
137+
name = "Categories",
138+
slug = "category",
139+
description = "Test categories",
140+
visibility = visibility,
141+
restBase = "categories",
142+
restNamespace = "wp/v2",
143+
types = listOf("post"),
144+
hierarchical = true,
145+
showCloud = true,
146+
capabilities = mock(),
147+
labels = mock()
148+
)
149+
150+
val tagDetails = TaxonomyTypeDetailsWithEditContext(
151+
name = "Tags",
152+
slug = "post_tag",
153+
description = "Test tags",
154+
visibility = visibility,
155+
restBase = "tags",
156+
restNamespace = "wp/v2",
157+
types = listOf("post"),
158+
hierarchical = false,
159+
showCloud = true,
160+
capabilities = mock(),
161+
labels = mock()
162+
)
163+
164+
return TaxonomyTypesResponseWithEditContext(
165+
mapOf(
166+
TaxonomyType.Category to categoryDetails,
167+
TaxonomyType.PostTag to tagDetails
168+
)
169+
)
170+
}
171+
}

0 commit comments

Comments
 (0)