From d0d3935a55c9d650a94dbe73caad24cfa1222af1 Mon Sep 17 00:00:00 2001 From: cooltey Date: Mon, 24 Mar 2025 17:30:18 -0700 Subject: [PATCH 1/4] Experimental: show categories by articles --- .../org/wikipedia/history/HistoryFragment.kt | 43 +++++++++++++++++++ .../org/wikipedia/history/HistoryViewModel.kt | 16 +++++++ app/src/main/res/layout/fragment_history.xml | 10 +++++ 3 files changed, 69 insertions(+) diff --git a/app/src/main/java/org/wikipedia/history/HistoryFragment.kt b/app/src/main/java/org/wikipedia/history/HistoryFragment.kt index b3036f0c567..bb8e56fc149 100644 --- a/app/src/main/java/org/wikipedia/history/HistoryFragment.kt +++ b/app/src/main/java/org/wikipedia/history/HistoryFragment.kt @@ -22,6 +22,8 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.google.android.material.chip.Chip import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.wikipedia.BackPressedHandler import org.wikipedia.Constants @@ -90,6 +92,47 @@ class HistoryFragment : Fragment(), BackPressedHandler { onPagesDeleted() } } + + binding.showCategoriesDialog.setOnClickListener { + val groupData = viewModel.groupedTitles + val recyclerView = RecyclerView(requireContext()).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + layoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL) // 2 columns, vertical stagger + adapter = ChipAdapter(groupData.toList()) // Convert groupData to a list + } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Categories") + .setView(recyclerView) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + + class ChipAdapter(private val data: List>) : + RecyclerView.Adapter() { + + inner class ChipViewHolder(val chip: Chip) : RecyclerView.ViewHolder(chip) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChipViewHolder { + val chip = Chip(parent.context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + return ChipViewHolder(chip) + } + + override fun onBindViewHolder(holder: ChipViewHolder, position: Int) { + val (title, count) = data[position] + holder.chip.text = "$title ($count)" + } + + override fun getItemCount(): Int = data.size } private fun setUpScrollListener() { diff --git a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt index d2df6169045..77334d7ed56 100644 --- a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt +++ b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt @@ -10,8 +10,12 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.wikipedia.database.AppDatabase +import org.wikipedia.dataclient.ServiceFactory +import org.wikipedia.dataclient.WikiSite +import org.wikipedia.page.PageTitle import org.wikipedia.util.Resource import org.wikipedia.util.SingleLiveData +import org.wikipedia.util.StringUtil class HistoryViewModel : ViewModel() { @@ -33,6 +37,7 @@ class HistoryViewModel : ViewModel() { val historyItems = MutableLiveData(Resource>()) val deleteHistoryItemsAction = SingleLiveData>() + var groupedTitles = emptyList>() init { reloadHistoryItems() @@ -47,6 +52,17 @@ class HistoryViewModel : ViewModel() { private suspend fun loadHistoryItems() { withContext(Dispatchers.IO) { val items = AppDatabase.instance.historyEntryWithImageDao().filterHistoryItems(searchQuery.orEmpty()) + val lang = "en" + val combinedTitles = items.filterIsInstance().filter { it.lang == lang }.take(50).map { it.apiTitle } + val response = ServiceFactory.get(WikiSite.forLanguageCode(lang)).getCategories(combinedTitles.joinToString("|")) + val titles = response.query?.pages?.map { page -> + PageTitle(page.title, WikiSite.forLanguageCode(lang)).also { + it.displayText = page.displayTitle(lang) + } + }.orEmpty() + + // Grouping the titles by setting in a Pair<> with its name and count + groupedTitles = titles.groupBy { it.prefixedText }.map { Pair(StringUtil.removeNamespace(it.key), it.value.size) }.sortedByDescending { it.second } historyItems.postValue(Resource.Success(items)) } } diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index 4996d059640..aab017ff307 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -55,6 +55,16 @@ android:layout_height="match_parent" android:scrollbars="vertical" /> + + Date: Wed, 26 Mar 2025 17:10:34 -0700 Subject: [PATCH 2/4] Use the correct API call for the categories --- .../java/org/wikipedia/dataclient/Service.kt | 3 + .../wikipedia/dataclient/mwapi/MwQueryPage.kt | 7 ++ .../org/wikipedia/history/HistoryFragment.kt | 76 ++++++++----------- .../org/wikipedia/history/HistoryViewModel.kt | 28 +++++-- app/src/main/res/layout/fragment_history.xml | 10 --- .../view_history_header_with_search.xml | 12 +++ 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/wikipedia/dataclient/Service.kt b/app/src/main/java/org/wikipedia/dataclient/Service.kt index b14b0c4b9e4..4a20139557a 100644 --- a/app/src/main/java/org/wikipedia/dataclient/Service.kt +++ b/app/src/main/java/org/wikipedia/dataclient/Service.kt @@ -172,6 +172,9 @@ interface Service { @GET(MW_API_PREFIX + "action=query&prop=info&generator=categories&inprop=varianttitles|displaytitle&gclshow=!hidden&gcllimit=500") suspend fun getCategories(@Query("titles") titles: String): MwQueryResponse + @GET(MW_API_PREFIX + "action=query&prop=categories&gclshow=!hidden&gcllimit=100") + suspend fun getCategoriesProps(@Query("titles") titles: String): MwQueryResponse + @GET(MW_API_PREFIX + "action=query&prop=description|pageimages|info&pilicense=any&generator=categorymembers&inprop=varianttitles|displaytitle&gcmprop=ids|title") suspend fun getCategoryMembers( @Query("gcmtitle") title: String, diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt index aba6fbbd8ac..40dd00fa90f 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt @@ -24,6 +24,7 @@ class MwQueryPage { @SerialName("pageid") val pageId = 0 @SerialName("pageprops") val pageProps: PageProps? = null @SerialName("entityterms") val entityTerms: EntityTerms? = null + @SerialName("categories") val categoriesProps: List? = null val ns = 0 val coordinates: List? = null @@ -147,4 +148,10 @@ class MwQueryPage { val label: List = emptyList() val description: List = emptyList() } + + @Serializable + class Category { + val ns = 0 + val title: String = "" + } } diff --git a/app/src/main/java/org/wikipedia/history/HistoryFragment.kt b/app/src/main/java/org/wikipedia/history/HistoryFragment.kt index bb8e56fc149..99a1d0a72fd 100644 --- a/app/src/main/java/org/wikipedia/history/HistoryFragment.kt +++ b/app/src/main/java/org/wikipedia/history/HistoryFragment.kt @@ -9,11 +9,13 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout +import android.widget.ScrollView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible +import androidx.core.view.setPadding import androidx.core.view.updateLayoutParams import androidx.core.view.updateMarginsRelative import androidx.fragment.app.Fragment @@ -22,8 +24,6 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.StaggeredGridLayoutManager -import com.google.android.material.chip.Chip import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.wikipedia.BackPressedHandler import org.wikipedia.Constants @@ -40,6 +40,7 @@ import org.wikipedia.util.DimenUtil import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.Resource import org.wikipedia.util.ResourceUtil +import org.wikipedia.util.StringUtil import org.wikipedia.util.log.L import org.wikipedia.views.DefaultViewHolder import org.wikipedia.views.PageItemView @@ -92,49 +93,7 @@ class HistoryFragment : Fragment(), BackPressedHandler { onPagesDeleted() } } - - binding.showCategoriesDialog.setOnClickListener { - val groupData = viewModel.groupedTitles - val recyclerView = RecyclerView(requireContext()).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - layoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL) // 2 columns, vertical stagger - adapter = ChipAdapter(groupData.toList()) // Convert groupData to a list - } - - MaterialAlertDialogBuilder(requireContext()) - .setTitle("Categories") - .setView(recyclerView) - .setPositiveButton(android.R.string.ok, null) - .show() - } - } - - class ChipAdapter(private val data: List>) : - RecyclerView.Adapter() { - - inner class ChipViewHolder(val chip: Chip) : RecyclerView.ViewHolder(chip) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChipViewHolder { - val chip = Chip(parent.context).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - return ChipViewHolder(chip) - } - - override fun onBindViewHolder(holder: ChipViewHolder, position: Int) { - val (title, count) = data[position] - holder.chip.text = "$title ($count)" - } - - override fun getItemCount(): Int = data.size } - private fun setUpScrollListener() { binding.historyList.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { @@ -303,6 +262,7 @@ class HistoryFragment : Fragment(), BackPressedHandler { init { val searchCardView = itemView.findViewById(R.id.search_card) val voiceSearchButton = itemView.findViewById(R.id.voice_search_button) + val categoriesDialogButton = itemView.findViewById(R.id.categories_dialog) historyFilterButton = itemView.findViewById(R.id.history_filter) clearHistoryButton = itemView.findViewById(R.id.history_delete) searchCardView.setOnClickListener { (requireParentFragment() as MainFragment).openSearchActivity(Constants.InvokeSource.NAV_MENU, null, it) } @@ -325,6 +285,34 @@ class HistoryFragment : Fragment(), BackPressedHandler { deleteSelectedPages() } } + categoriesDialogButton.setOnClickListener { + val groupData = viewModel.groupedTitles + + var htmlContent = "" + groupData.forEach { + htmlContent += "${it.first} (${it.second})
" + } + + val textView = TextView(requireContext()).apply { + text = StringUtil.fromHtml(htmlContent) + textSize = 14f + } + + val scrollView = ScrollView(requireContext()).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setPadding(DimenUtil.roundedDpToPx(12f)) + addView(textView) + } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.action_item_categories) + .setView(scrollView) + .setPositiveButton(android.R.string.ok, null) + .show() + } FeedbackUtil.setButtonTooltip(historyFilterButton, clearHistoryButton) adjustSearchCardView(searchCardView) } diff --git a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt index 77334d7ed56..28dfad0d00a 100644 --- a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt +++ b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt @@ -6,13 +6,15 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.wikipedia.database.AppDatabase import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite -import org.wikipedia.page.PageTitle +import org.wikipedia.dataclient.mwapi.MwQueryPage.Category import org.wikipedia.util.Resource import org.wikipedia.util.SingleLiveData import org.wikipedia.util.StringUtil @@ -53,16 +55,26 @@ class HistoryViewModel : ViewModel() { withContext(Dispatchers.IO) { val items = AppDatabase.instance.historyEntryWithImageDao().filterHistoryItems(searchQuery.orEmpty()) val lang = "en" - val combinedTitles = items.filterIsInstance().filter { it.lang == lang }.take(50).map { it.apiTitle } - val response = ServiceFactory.get(WikiSite.forLanguageCode(lang)).getCategories(combinedTitles.joinToString("|")) - val titles = response.query?.pages?.map { page -> - PageTitle(page.title, WikiSite.forLanguageCode(lang)).also { - it.displayText = page.displayTitle(lang) + val historyEntryItems = items.filterIsInstance() + + val categories = mutableListOf() + val deferredCategories = historyEntryItems.map { item -> + async { + try { + val response = ServiceFactory.get(WikiSite.forLanguageCode(lang)).getCategoriesProps(item.apiTitle) + response.query?.firstPage()?.categoriesProps ?: emptyList() + } catch (e: Exception) { + // Handle exceptions appropriately, e.g., log, return emptyList, etc. + println("Error fetching categories for ${item.apiTitle}: ${e.message}") + emptyList() // or throw e; depending on your error handling. + } } - }.orEmpty() + } + + deferredCategories.awaitAll().forEach { categories.addAll(it) } // Grouping the titles by setting in a Pair<> with its name and count - groupedTitles = titles.groupBy { it.prefixedText }.map { Pair(StringUtil.removeNamespace(it.key), it.value.size) }.sortedByDescending { it.second } + groupedTitles = categories.groupBy { it.title }.map { Pair(StringUtil.removeNamespace(it.key), it.value.size) }.sortedByDescending { it.second } historyItems.postValue(Resource.Success(items)) } } diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index aab017ff307..4996d059640 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -55,16 +55,6 @@ android:layout_height="match_parent" android:scrollbars="vertical" /> - - + + Date: Wed, 26 Mar 2025 17:15:51 -0700 Subject: [PATCH 3/4] parameters --- app/src/main/java/org/wikipedia/dataclient/Service.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/wikipedia/dataclient/Service.kt b/app/src/main/java/org/wikipedia/dataclient/Service.kt index 4a20139557a..6f1efdee53b 100644 --- a/app/src/main/java/org/wikipedia/dataclient/Service.kt +++ b/app/src/main/java/org/wikipedia/dataclient/Service.kt @@ -172,7 +172,7 @@ interface Service { @GET(MW_API_PREFIX + "action=query&prop=info&generator=categories&inprop=varianttitles|displaytitle&gclshow=!hidden&gcllimit=500") suspend fun getCategories(@Query("titles") titles: String): MwQueryResponse - @GET(MW_API_PREFIX + "action=query&prop=categories&gclshow=!hidden&gcllimit=100") + @GET(MW_API_PREFIX + "action=query&prop=categories&clshow=!hidden&cllimit=100") suspend fun getCategoriesProps(@Query("titles") titles: String): MwQueryResponse @GET(MW_API_PREFIX + "action=query&prop=description|pageimages|info&pilicense=any&generator=categorymembers&inprop=varianttitles|displaytitle&gcmprop=ids|title") From 79e1123cd690b2e9160af1625ad0f1fbf90d1bdb Mon Sep 17 00:00:00 2001 From: cooltey Date: Thu, 27 Mar 2025 16:05:13 -0700 Subject: [PATCH 4/4] For primary language --- app/src/main/java/org/wikipedia/history/HistoryViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt index 28dfad0d00a..dbd5e0c9d48 100644 --- a/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt +++ b/app/src/main/java/org/wikipedia/history/HistoryViewModel.kt @@ -11,9 +11,9 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.wikipedia.WikipediaApp import org.wikipedia.database.AppDatabase import org.wikipedia.dataclient.ServiceFactory -import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage.Category import org.wikipedia.util.Resource import org.wikipedia.util.SingleLiveData @@ -54,14 +54,13 @@ class HistoryViewModel : ViewModel() { private suspend fun loadHistoryItems() { withContext(Dispatchers.IO) { val items = AppDatabase.instance.historyEntryWithImageDao().filterHistoryItems(searchQuery.orEmpty()) - val lang = "en" val historyEntryItems = items.filterIsInstance() val categories = mutableListOf() val deferredCategories = historyEntryItems.map { item -> async { try { - val response = ServiceFactory.get(WikiSite.forLanguageCode(lang)).getCategoriesProps(item.apiTitle) + val response = ServiceFactory.get(WikipediaApp.instance.wikiSite).getCategoriesProps(item.apiTitle) response.query?.firstPage()?.categoriesProps ?: emptyList() } catch (e: Exception) { // Handle exceptions appropriately, e.g., log, return emptyList, etc.