diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 867455674cd..f53d85eac76 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -313,8 +313,8 @@
android:theme="@style/AppTheme" />
>(PrefsIoUtil.getString(R.string.preference_key_user_contrib_filter_ns, null))
+ var userContribFilterExcludedNs
+ get() = JsonUtil.decodeFromString>(PrefsIoUtil.getString(R.string.preference_key_user_contrib_filter_excluded_ns, null))
?: emptySet()
- set(value) = PrefsIoUtil.setString(R.string.preference_key_user_contrib_filter_ns, JsonUtil.encodeToString(value))
+ set(value) = PrefsIoUtil.setString(R.string.preference_key_user_contrib_filter_excluded_ns, JsonUtil.encodeToString(value))
+
+ var userContribFilterLangCode
+ get() = PrefsIoUtil.getString(R.string.preference_key_user_contrib_filter_lang_code, WikipediaApp.instance.appOrSystemLanguageCode)!!
+ set(value) = PrefsIoUtil.setString(R.string.preference_key_user_contrib_filter_lang_code, value)
var editSyntaxHighlightEnabled
get() = PrefsIoUtil.getBoolean(R.string.preference_key_edit_syntax_highlight, true)
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterActivity.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterActivity.kt
new file mode 100644
index 00000000000..400f4b9b45a
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterActivity.kt
@@ -0,0 +1,187 @@
+package org.wikipedia.usercontrib
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import org.wikipedia.Constants
+import org.wikipedia.R
+import org.wikipedia.WikipediaApp
+import org.wikipedia.activity.BaseActivity
+import org.wikipedia.databinding.ActivityUserContribWikiSelectBinding
+import org.wikipedia.page.Namespace
+import org.wikipedia.settings.Prefs
+import org.wikipedia.settings.languages.WikipediaLanguagesActivity
+import org.wikipedia.staticdata.TalkAliasData
+import org.wikipedia.staticdata.UserAliasData
+import org.wikipedia.staticdata.UserTalkAliasData
+import org.wikipedia.views.DefaultViewHolder
+
+class UserContribFilterActivity : BaseActivity() {
+
+ private lateinit var binding: ActivityUserContribWikiSelectBinding
+
+ private val langUpdateLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ binding.recyclerView.adapter = ItemAdapter(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityUserContribWikiSelectBinding.inflate(layoutInflater)
+
+ binding.recyclerView.layoutManager = LinearLayoutManager(this)
+ binding.recyclerView.adapter = ItemAdapter(this)
+ binding.recyclerView.itemAnimator = null
+ setContentView(binding.root)
+ setResult(RESULT_OK)
+ }
+
+ inner class ItemViewHolder constructor(itemView: UserContribFilterItemView) :
+ DefaultViewHolder(itemView) {
+ fun bindItem(item: Item) {
+ view.setContents(item)
+ }
+ }
+
+ private inner class FilterHeaderViewHolder constructor(itemView: View) :
+ DefaultViewHolder(itemView) {
+ val headerText = itemView.findViewById(R.id.filter_header_title)!!
+
+ fun bindItem(filterHeader: String) {
+ headerText.text = filterHeader
+ }
+ }
+
+ private inner class AddLanguageViewHolder constructor(itemView: UserContribFilterItemView) :
+ DefaultViewHolder(itemView), UserContribFilterItemView.Callback {
+ fun bindItem(text: String) {
+ (itemView as UserContribFilterItemView).callback = this
+ itemView.setSingleLabel(text)
+ }
+
+ override fun onSelected(item: Item?) {
+ langUpdateLauncher.launch(WikipediaLanguagesActivity.newIntent(this@UserContribFilterActivity, Constants.InvokeSource.USER_CONTRIB_ACTIVITY))
+ }
+ }
+
+ private inner class ItemAdapter(val context: Context) : RecyclerView.Adapter>(), UserContribFilterItemView.Callback {
+ private val itemList = mutableListOf()
+
+ init {
+ itemList.add(getString(R.string.notifications_wiki_filter_header))
+ WikipediaApp.instance.languageState.appLanguageCodes.forEach { itemList.add(Item(FILTER_TYPE_WIKI, it, null)) }
+ itemList.add(Item(FILTER_TYPE_WIKI, Constants.WIKI_CODE_COMMONS, R.drawable.ic_commons_logo))
+ itemList.add(Item(FILTER_TYPE_WIKI, Constants.WIKI_CODE_WIKIDATA, R.drawable.ic_wikidata_logo))
+ itemList.add(getString(R.string.notifications_filter_update_app_languages))
+ itemList.add(getString(R.string.user_contrib_filter_ns_header))
+ itemList.add(Item(FILTER_TYPE_NAMESPACE, getString(R.string.user_contrib_filter_all), null))
+ itemList.add(Item(FILTER_TYPE_NAMESPACE, getString(R.string.namespace_article), R.drawable.ic_article_ltr_ooui))
+ itemList.add(Item(FILTER_TYPE_NAMESPACE, TalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode), R.drawable.ic_notification_article_talk))
+ itemList.add(Item(FILTER_TYPE_NAMESPACE, UserAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode), R.drawable.ic_user_avatar))
+ itemList.add(Item(FILTER_TYPE_NAMESPACE, UserTalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode), R.drawable.ic_notification_user_talk))
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, type: Int): DefaultViewHolder<*> {
+ return when (type) {
+ VIEW_TYPE_HEADER -> {
+ FilterHeaderViewHolder(layoutInflater.inflate(R.layout.view_notification_filter_header, parent, false))
+ }
+ VIEW_TYPE_ADD_LANGUAGE -> {
+ AddLanguageViewHolder(UserContribFilterItemView(context))
+ }
+ else -> {
+ val view = UserContribFilterItemView(context)
+ view.callback = this
+ ItemViewHolder(view)
+ }
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return itemList.size
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (itemList[position] is String && itemList[position] == getString(R.string.notifications_filter_update_app_languages)) VIEW_TYPE_ADD_LANGUAGE
+ else if (itemList[position] is String) VIEW_TYPE_HEADER
+ else VIEW_TYPE_ITEM
+ }
+
+ override fun onBindViewHolder(holder: DefaultViewHolder<*>, position: Int) {
+ when (holder) {
+ is FilterHeaderViewHolder -> holder.bindItem(itemList[position] as String)
+ is AddLanguageViewHolder -> holder.bindItem(itemList[position] as String)
+ else -> (holder as ItemViewHolder).bindItem(itemList[position] as Item)
+ }
+ }
+
+ override fun onSelected(item: Item?) {
+ item?.let {
+ if (it.type == FILTER_TYPE_WIKI) {
+ Prefs.userContribFilterLangCode = item.filterCode
+ } else if (it.type == FILTER_TYPE_NAMESPACE) {
+ var excludedNsFilter = Prefs.userContribFilterExcludedNs
+ when (val namespaceCode = getNamespaceCode(item.filterCode)) {
+ -1 -> { // Select "all"
+ excludedNsFilter = if (excludedNsFilter.isEmpty() || excludedNsFilter.size < NAMESPACE_LIST.size) {
+ NAMESPACE_LIST.toSet()
+ } else {
+ emptySet()
+ }
+ }
+ else -> {
+ excludedNsFilter = if (excludedNsFilter.contains(namespaceCode)) {
+ excludedNsFilter.minus(namespaceCode)
+ } else {
+ excludedNsFilter.plus(namespaceCode)
+ }
+ }
+ }
+ Prefs.userContribFilterExcludedNs = excludedNsFilter
+ }
+ }
+ notifyItemRangeChanged(0, itemCount)
+ }
+ }
+
+ private fun getNamespaceCode(text: String): Int {
+ return when (text) {
+ getString(R.string.namespace_article) -> Namespace.MAIN.code()
+ TalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode) -> Namespace.TALK.code()
+ UserAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode) -> Namespace.USER.code()
+ UserTalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode) -> Namespace.USER_TALK.code()
+ else -> -1
+ }
+ }
+
+ inner class Item constructor(val type: Int, val filterCode: String, val imageRes: Int? = null) {
+ fun isEnabled(): Boolean {
+ if (type == FILTER_TYPE_WIKI) {
+ return Prefs.userContribFilterLangCode == filterCode
+ }
+ val excludedNsFilter = Prefs.userContribFilterExcludedNs
+ if (filterCode == getString(R.string.user_contrib_filter_all)) {
+ return NAMESPACE_LIST.find { excludedNsFilter.contains(it) } == null
+ }
+ return !excludedNsFilter.contains(getNamespaceCode(filterCode))
+ }
+ }
+
+ companion object {
+ private const val VIEW_TYPE_HEADER = 0
+ private const val VIEW_TYPE_ITEM = 1
+ private const val VIEW_TYPE_ADD_LANGUAGE = 2
+ const val FILTER_TYPE_WIKI = 0
+ const val FILTER_TYPE_NAMESPACE = 1
+ val NAMESPACE_LIST = listOf(Namespace.MAIN.code(), Namespace.TALK.code(), Namespace.USER.code(), Namespace.USER_TALK.code())
+
+ fun newIntent(context: Context): Intent {
+ return Intent(context, UserContribFilterActivity::class.java)
+ }
+ }
+}
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterItemView.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterItemView.kt
new file mode 100644
index 00000000000..db7bbfd3852
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterItemView.kt
@@ -0,0 +1,84 @@
+package org.wikipedia.usercontrib
+
+import android.content.Context
+import android.graphics.Typeface
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.core.widget.ImageViewCompat
+import org.wikipedia.Constants
+import org.wikipedia.R
+import org.wikipedia.WikipediaApp
+import org.wikipedia.databinding.ItemUserContribFilterBinding
+import org.wikipedia.search.SearchFragment
+import org.wikipedia.util.DimenUtil
+import org.wikipedia.util.ResourceUtil
+import org.wikipedia.views.ViewUtil
+
+class UserContribFilterItemView constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
+
+ interface Callback {
+ fun onSelected(item: UserContribFilterActivity.Item?)
+ }
+
+ private var item: UserContribFilterActivity.Item? = null
+ private var binding = ItemUserContribFilterBinding.inflate(LayoutInflater.from(context), this)
+ private val labelTypeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
+ var callback: Callback? = null
+
+ init {
+ layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DimenUtil.roundedDpToPx(48f))
+ setBackgroundResource(ResourceUtil.getThemedAttributeId(context, R.attr.selectableItemBackground))
+ setOnClickListener {
+ callback?.onSelected(item)
+ }
+ }
+
+ fun setContents(item: UserContribFilterActivity.Item) {
+ this.item = item
+ binding.itemTitle.text = WikipediaApp.instance.languageState.getWikiLanguageName(item.filterCode)
+ binding.itemCheck.isVisible = item.isEnabled()
+
+ if (item.type == UserContribFilterActivity.FILTER_TYPE_WIKI) {
+ getTitleCodeFor(item.filterCode)?.let {
+ binding.languageCode.text = it
+ binding.languageCode.visibility = View.VISIBLE
+ ViewUtil.formatLangButton(binding.languageCode, it, SearchFragment.LANG_BUTTON_TEXT_SIZE_SMALLER, SearchFragment.LANG_BUTTON_TEXT_SIZE_LARGER)
+ } ?: run {
+ binding.languageCode.visibility = View.GONE
+ }
+ binding.itemCheck.setImageResource(R.drawable.ic_baseline_radio_button_checked_24)
+ } else {
+ binding.languageCode.visibility = View.GONE
+ binding.itemCheck.setImageResource(R.drawable.ic_check_borderless)
+ }
+
+ item.imageRes?.let {
+ binding.itemLogo.setImageResource(it)
+ binding.itemLogo.visibility = View.VISIBLE
+ } ?: run {
+ binding.itemLogo.visibility = if (binding.languageCode.isVisible) View.GONE else View.INVISIBLE
+ }
+ }
+
+ fun setSingleLabel(text: String) {
+ binding.languageCode.visibility = View.GONE
+ binding.itemLogo.visibility = View.VISIBLE
+ binding.itemLogo.setImageResource(R.drawable.ic_mode_edit_themed_24dp)
+ binding.itemCheck.visibility = View.GONE
+ binding.itemTitle.setTextColor(ResourceUtil.getThemedColorStateList(context, R.attr.colorAccent))
+ binding.itemTitle.text = text.uppercase()
+ binding.itemTitle.typeface = labelTypeface
+ binding.itemTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
+ ImageViewCompat.setImageTintList(binding.itemLogo, ResourceUtil.getThemedColorStateList(context, R.attr.colorAccent))
+ }
+
+ private fun getTitleCodeFor(itemCode: String): String? {
+ return if (itemCode == Constants.WIKI_CODE_COMMONS || itemCode == Constants.WIKI_CODE_WIKIDATA) null
+ else itemCode
+ }
+}
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterOverflowView.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterOverflowView.kt
deleted file mode 100644
index 3c5cc5b4105..00000000000
--- a/app/src/main/java/org/wikipedia/usercontrib/UserContribFilterOverflowView.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.wikipedia.usercontrib
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.PopupWindow
-import androidx.core.widget.PopupWindowCompat
-import org.wikipedia.WikipediaApp
-import org.wikipedia.databinding.ViewUserContribFilterOverflowBinding
-import org.wikipedia.page.Namespace
-import org.wikipedia.settings.Prefs
-import org.wikipedia.staticdata.TalkAliasData
-import org.wikipedia.staticdata.UserAliasData
-import org.wikipedia.staticdata.UserTalkAliasData
-
-class UserContribFilterOverflowView(context: Context) : FrameLayout(context) {
-
- fun interface Callback {
- fun onItemClicked()
- }
-
- private var binding = ViewUserContribFilterOverflowBinding.inflate(LayoutInflater.from(context), this, true)
- private var callback: Callback? = null
- private var popupWindowHost: PopupWindow? = null
-
- init {
- setButtonsListener()
- }
-
- fun show(anchorView: View, callback: Callback?) {
- this.callback = callback
-
- binding.nsUserText.text = UserAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode)
- binding.nsTalkText.text = TalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode)
- binding.nsUserTalkText.text = UserTalkAliasData.valueFor(WikipediaApp.instance.appOrSystemLanguageCode)
- updateSelectedItem()
-
- popupWindowHost = PopupWindow(this, ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, true)
- popupWindowHost?.let {
- it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
- PopupWindowCompat.setOverlapAnchor(it, true)
- it.showAsDropDown(anchorView, 0, 0, Gravity.END)
- }
- popupWindowHost?.setOnDismissListener {
- popupWindowHost = null
- }
- }
-
- private fun updateSelectedItem() {
- binding.nsNoneCheckIcon.visibility = View.INVISIBLE
- binding.nsArticleCheckIcon.visibility = View.INVISIBLE
- binding.nsTalkCheckIcon.visibility = View.INVISIBLE
- binding.nsUserCheckIcon.visibility = View.INVISIBLE
- binding.nsUserTalkCheckIcon.visibility = View.INVISIBLE
-
- if (Prefs.userContribFilterNs.isEmpty()) {
- binding.nsNoneCheckIcon.visibility = View.VISIBLE
- } else {
- Prefs.userContribFilterNs.forEach {
- when (it) {
- Namespace.MAIN.code() -> { binding.nsArticleCheckIcon.visibility = View.VISIBLE }
- Namespace.TALK.code() -> { binding.nsTalkCheckIcon.visibility = View.VISIBLE }
- Namespace.USER.code() -> { binding.nsUserCheckIcon.visibility = View.VISIBLE }
- Namespace.USER_TALK.code() -> { binding.nsUserTalkCheckIcon.visibility = View.VISIBLE }
- }
- }
- }
- }
-
- private fun setButtonsListener() {
- binding.nsNoneButton.setOnClickListener {
- Prefs.userContribFilterNs = emptySet()
- onSelected()
- }
- binding.nsArticleButton.setOnClickListener {
- Prefs.userContribFilterNs = Prefs.userContribFilterNs.plus(Namespace.MAIN.code())
- onSelected()
- }
- binding.nsTalkButton.setOnClickListener {
- Prefs.userContribFilterNs = Prefs.userContribFilterNs.plus(Namespace.TALK.code())
- onSelected()
- }
- binding.nsUserButton.setOnClickListener {
- Prefs.userContribFilterNs = Prefs.userContribFilterNs.plus(Namespace.USER.code())
- onSelected()
- }
- binding.nsUserTalkButton.setOnClickListener {
- Prefs.userContribFilterNs = Prefs.userContribFilterNs.plus(Namespace.USER_TALK.code())
- onSelected()
- }
- }
-
- private fun onSelected() {
- callback?.onItemClicked()
- popupWindowHost?.dismiss()
- }
-}
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribListActivity.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribListActivity.kt
index 6bd2c931aff..e1f29855149 100644
--- a/app/src/main/java/org/wikipedia/usercontrib/UserContribListActivity.kt
+++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribListActivity.kt
@@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import org.wikipedia.Constants
import org.wikipedia.R
-import org.wikipedia.WikipediaApp
import org.wikipedia.activity.BaseActivity
import org.wikipedia.databinding.ActivityUserContribBinding
import org.wikipedia.databinding.ViewEditHistoryEmptyMessagesBinding
@@ -45,14 +44,11 @@ import org.wikipedia.page.LinkHandler
import org.wikipedia.page.LinkMovementMethodExt
import org.wikipedia.page.PageTitle
import org.wikipedia.richtext.RichTextUtil
-import org.wikipedia.search.SearchFragment
import org.wikipedia.settings.Prefs
import org.wikipedia.talk.UserTalkPopupHelper
import org.wikipedia.util.*
import org.wikipedia.views.SearchAndFilterActionProvider
-import org.wikipedia.views.ViewUtil
import org.wikipedia.views.WikiErrorView
-import java.util.*
class UserContribListActivity : BaseActivity() {
@@ -73,16 +69,14 @@ class UserContribListActivity : BaseActivity() {
linkHandler.onUrlClick(url, title, linkText, x, y)
}
- private val requestLanguageChange = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ private val launchFilterActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
- it.data?.let { intent ->
- viewModel.langCode = intent.getStringExtra(UserContribWikiSelectActivity.INTENT_EXTRA_SELECT_LANG_CODE).orEmpty()
- .ifEmpty { WikipediaApp.instance.appOrSystemLanguageCode }
- updateLangButton()
- viewModel.clearCache()
- viewModel.loadStats()
- userContribListAdapter.reload()
- }
+ viewModel.langCode = Prefs.userContribFilterLangCode
+ viewModel.loadStats()
+ setupAdapters()
+ viewModel.clearCache()
+ userContribListAdapter.reload()
+ userContribSearchBarAdapter.notifyItemChanged(0)
}
}
@@ -112,11 +106,6 @@ class UserContribListActivity : BaseActivity() {
}
})
- binding.langButtonContainer.setOnClickListener {
- requestLanguageChange.launch(UserContribWikiSelectActivity.newIntent(this, viewModel.langCode))
- }
- updateLangButton()
-
lifecycleScope.launchWhenCreated {
userContribListAdapter.loadStateFlow.distinctUntilChangedBy { it.refresh }
.filter { it.refresh is LoadState.NotLoading }
@@ -158,30 +147,6 @@ class UserContribListActivity : BaseActivity() {
}
}
- private fun updateLangButton() {
- linkHandler.wikiSite = viewModel.wikiSite
- when (viewModel.langCode) {
- Constants.WIKI_CODE_WIKIDATA -> {
- binding.langButtonText.isVisible = false
- binding.langButtonIcon.setImageResource(R.drawable.ic_wikidata_logo)
- binding.langButtonIcon.isVisible = true
- }
- Constants.WIKI_CODE_COMMONS -> {
- binding.langButtonText.isVisible = false
- binding.langButtonIcon.setImageResource(R.drawable.ic_commons_logo)
- binding.langButtonIcon.isVisible = true
- }
- else -> {
- binding.langButtonText.isVisible = true
- binding.langButtonIcon.isVisible = false
- binding.langButtonText.text = viewModel.langCode.uppercase(Locale.ENGLISH)
- ViewUtil.formatLangButton(binding.langButtonText, binding.langButtonText.text.toString(),
- SearchFragment.LANG_BUTTON_TEXT_SIZE_SMALLER, SearchFragment.LANG_BUTTON_TEXT_SIZE_LARGER)
- }
- }
- FeedbackUtil.setButtonLongPressToast(binding.langButtonContainer)
- }
-
private fun setupAdapters() {
if (actionMode != null) {
binding.userContribRecycler.adapter = userContribListAdapter.withLoadStateFooter(loadFooter)
@@ -198,24 +163,6 @@ class UserContribListActivity : BaseActivity() {
actionMode = startSupportActionMode(searchActionModeCallback)
}
- fun showFilterOverflowMenu() {
- val editCountsValue = viewModel.userContribStatsData.value
- if (editCountsValue is Resource.Success) {
- val anchorView = if (actionMode != null && searchActionModeCallback.searchAndFilterActionProvider != null)
- searchActionModeCallback.searchBarFilterIcon!! else if (userContribSearchBarAdapter.viewHolder != null)
- userContribSearchBarAdapter.viewHolder!!.binding.filterByButton else binding.root
- UserContribFilterOverflowView(this@UserContribListActivity).show(anchorView) {
- setupAdapters()
- viewModel.clearCache()
- userContribListAdapter.reload()
- userContribSearchBarAdapter.notifyItemChanged(0)
- actionMode?.let {
- searchActionModeCallback.updateFilterIconAndText()
- }
- }
- }
- }
-
private inner class SearchBarAdapter : RecyclerView.Adapter() {
var viewHolder: SearchBarViewHolder? = null
override fun onBindViewHolder(holder: SearchBarViewHolder, position: Int) {
@@ -361,7 +308,7 @@ class UserContribListActivity : BaseActivity() {
}
binding.filterByButton.setOnClickListener {
- showFilterOverflowMenu()
+ launchFilterActivity.launch(UserContribFilterActivity.newIntent(this@UserContribListActivity))
}
FeedbackUtil.setButtonLongPressToast(binding.filterByButton)
@@ -370,13 +317,14 @@ class UserContribListActivity : BaseActivity() {
}
private fun updateFilterCount() {
- if (Prefs.userContribFilterNs.isEmpty()) {
+ val excludedFilters = viewModel.excludedFiltersCount()
+ if (excludedFilters == 0) {
binding.filterCount.visibility = View.GONE
ImageViewCompat.setImageTintList(binding.filterByButton,
ResourceUtil.getThemedColorStateList(this@UserContribListActivity, R.attr.color_group_9))
} else {
binding.filterCount.visibility = View.VISIBLE
- binding.filterCount.text = Prefs.userContribFilterNs.size.toString()
+ binding.filterCount.text = excludedFilters.toString()
ImageViewCompat.setImageTintList(binding.filterByButton,
ResourceUtil.getThemedColorStateList(this@UserContribListActivity, R.attr.colorAccent))
}
@@ -386,14 +334,13 @@ class UserContribListActivity : BaseActivity() {
private inner class EmptyMessagesViewHolder constructor(val binding: ViewEditHistoryEmptyMessagesBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.emptySearchMessage.movementMethod = LinkMovementMethodExt { _ ->
- showFilterOverflowMenu()
+ launchFilterActivity.launch(UserContribFilterActivity.newIntent(this@UserContribListActivity))
}
}
fun bindItem() {
binding.emptySearchMessage.text = StringUtil.fromHtml(getString(R.string.page_edit_history_empty_search_message))
RichTextUtil.removeUnderlinesFromLinks(binding.emptySearchMessage)
- binding.searchEmptyContainer.isVisible = Prefs.userContribFilterNs.isNotEmpty()
}
}
@@ -415,7 +362,6 @@ class UserContribListActivity : BaseActivity() {
private inner class SearchCallback : SearchActionModeCallback() {
var searchAndFilterActionProvider: SearchAndFilterActionProvider? = null
- val searchBarFilterIcon get() = searchAndFilterActionProvider?.filterIcon
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
searchAndFilterActionProvider = SearchAndFilterActionProvider(this@UserContribListActivity, searchHintString,
@@ -428,11 +374,11 @@ class UserContribListActivity : BaseActivity() {
}
override fun onFilterIconClick() {
- showFilterOverflowMenu()
+ launchFilterActivity.launch(UserContribFilterActivity.newIntent(this@UserContribListActivity))
}
override fun getExcludedFilterCount(): Int {
- return Prefs.userContribFilterNs.size
+ return Prefs.userContribFilterExcludedNs.size
}
override fun getFilterIconContentDescription(): Int {
@@ -473,10 +419,6 @@ class UserContribListActivity : BaseActivity() {
override fun getParentContext(): Context {
return this@UserContribListActivity
}
-
- fun updateFilterIconAndText() {
- searchAndFilterActionProvider?.updateFilterIconAndText()
- }
}
internal inner class UserContribLinkHandler internal constructor(context: Context) : LinkHandler(context) {
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt
index 76fd525242d..e253e009dcb 100644
--- a/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt
+++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt
@@ -6,8 +6,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.paging.*
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.wikipedia.Constants
import org.wikipedia.WikipediaApp
import org.wikipedia.dataclient.Service
@@ -70,6 +73,11 @@ class UserContribListViewModel(bundle: Bundle) : ViewModel() {
loadStats()
}
+ fun excludedFiltersCount(): Int {
+ val excludedNsFilter = Prefs.userContribFilterExcludedNs
+ return UserContribFilterActivity.NAMESPACE_LIST.count { excludedNsFilter.contains(it) }
+ }
+
fun loadStats() {
viewModelScope.launch(CoroutineExceptionHandler { _, throwable ->
L.e(throwable)
@@ -95,7 +103,11 @@ class UserContribListViewModel(bundle: Bundle) : ViewModel() {
return LoadResult.Page(cachedContribs, null, cachedContinueKey)
}
- val nsFilter = Prefs.userContribFilterNs.joinToString("|")
+ if (excludedFiltersCount() == UserContribFilterActivity.NAMESPACE_LIST.size) {
+ return LoadResult.Page(emptyList(), null, null)
+ }
+
+ val nsFilter = UserContribFilterActivity.NAMESPACE_LIST.filter { !Prefs.userContribFilterExcludedNs.contains(it) }.joinToString("|")
val response = ServiceFactory.get(wikiSite).getUserContrib(userName, 500, nsFilter.ifEmpty { null }, null, params.key)
val contribs = response.query?.userContributions!!
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectActivity.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectActivity.kt
deleted file mode 100644
index d422a77ffe7..00000000000
--- a/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectActivity.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.wikipedia.usercontrib
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.view.ViewGroup
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import org.wikipedia.Constants
-import org.wikipedia.R
-import org.wikipedia.WikipediaApp
-import org.wikipedia.activity.BaseActivity
-import org.wikipedia.databinding.ActivityUserContribWikiSelectBinding
-import org.wikipedia.settings.languages.WikipediaLanguagesActivity
-import org.wikipedia.views.DefaultViewHolder
-
-class UserContribWikiSelectActivity : BaseActivity() {
-
- private lateinit var binding: ActivityUserContribWikiSelectBinding
- private lateinit var selectLangCode: String
-
- private val langUpdateLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- binding.recyclerView.adapter = WikiSelectAdapter(this)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityUserContribWikiSelectBinding.inflate(layoutInflater)
- selectLangCode = intent.getStringExtra(INTENT_EXTRA_SELECT_LANG_CODE).orEmpty().ifEmpty { WikipediaApp.instance.appOrSystemLanguageCode }
-
- binding.recyclerView.layoutManager = LinearLayoutManager(this)
- binding.recyclerView.adapter = WikiSelectAdapter(this)
- setContentView(binding.root)
- }
-
- inner class WikiSelectItemViewHolder constructor(itemView: UserContribWikiSelectItemView) :
- DefaultViewHolder(itemView) {
- fun bindItem(item: Item) {
- view.setContents(item, item.itemCode == selectLangCode)
- }
- }
-
- private inner class WikiSelectAddLanguageViewHolder constructor(itemView: UserContribWikiSelectItemView) :
- DefaultViewHolder(itemView), UserContribWikiSelectItemView.Callback {
- fun bindItem(text: String) {
- (itemView as UserContribWikiSelectItemView).callback = this
- itemView.setSingleLabel(text)
- }
-
- override fun onSelected(item: Item?) {
- langUpdateLauncher.launch(WikipediaLanguagesActivity.newIntent(this@UserContribWikiSelectActivity, Constants.InvokeSource.USER_CONTRIB_ACTIVITY))
- }
- }
-
- private inner class WikiSelectAdapter(val context: Context) : RecyclerView.Adapter>(), UserContribWikiSelectItemView.Callback {
- private val itemList = mutableListOf()
-
- init {
- WikipediaApp.instance.languageState.appLanguageCodes.forEach { itemList.add(Item(it, null)) }
- itemList.add(Item(Constants.WIKI_CODE_COMMONS, R.drawable.ic_commons_logo))
- itemList.add(Item(Constants.WIKI_CODE_WIKIDATA, R.drawable.ic_wikidata_logo))
- itemList.add(getString(R.string.notifications_filter_update_app_languages))
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, type: Int): DefaultViewHolder<*> {
- return when (type) {
- VIEW_TYPE_ADD_LANGUAGE -> {
- WikiSelectAddLanguageViewHolder(UserContribWikiSelectItemView(context))
- }
- else -> {
- val view = UserContribWikiSelectItemView(context)
- view.callback = this
- WikiSelectItemViewHolder(view)
- }
- }
- }
-
- override fun getItemCount(): Int {
- return itemList.size
- }
-
- override fun getItemViewType(position: Int): Int {
- return if (itemList[position] is String && itemList[position] == getString(R.string.notifications_filter_update_app_languages)) VIEW_TYPE_ADD_LANGUAGE
- else VIEW_TYPE_ITEM
- }
-
- override fun onBindViewHolder(holder: DefaultViewHolder<*>, position: Int) {
- when (holder) {
- is WikiSelectAddLanguageViewHolder -> holder.bindItem(itemList[position] as String)
- else -> (holder as WikiSelectItemViewHolder).bindItem(itemList[position] as Item)
- }
- }
-
- override fun onSelected(item: Item?) {
- setResult(RESULT_OK, intent.putExtra(INTENT_EXTRA_SELECT_LANG_CODE, item?.itemCode))
- finish()
- }
- }
-
- inner class Item constructor(val itemCode: String, val imageRes: Int? = null)
-
- companion object {
- private const val VIEW_TYPE_ITEM = 1
- private const val VIEW_TYPE_ADD_LANGUAGE = 2
-
- const val INTENT_EXTRA_SELECT_LANG_CODE = "selectLangCode"
- const val ACTIVITY_RESULT_LANGUAGES_CHANGED = 2
-
- fun newIntent(context: Context, selectLangCode: String): Intent {
- return Intent(context, UserContribWikiSelectActivity::class.java)
- .putExtra(INTENT_EXTRA_SELECT_LANG_CODE, selectLangCode)
- }
- }
-}
diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectItemView.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectItemView.kt
deleted file mode 100644
index abe71b571b4..00000000000
--- a/app/src/main/java/org/wikipedia/usercontrib/UserContribWikiSelectItemView.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.wikipedia.usercontrib
-
-import android.content.Context
-import android.graphics.Typeface
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.core.view.isVisible
-import androidx.core.widget.ImageViewCompat
-import org.wikipedia.Constants
-import org.wikipedia.R
-import org.wikipedia.WikipediaApp
-import org.wikipedia.databinding.ItemUserContribWikiSelectBinding
-import org.wikipedia.search.SearchFragment
-import org.wikipedia.util.DimenUtil
-import org.wikipedia.util.ResourceUtil
-import org.wikipedia.views.ViewUtil
-
-class UserContribWikiSelectItemView constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
-
- interface Callback {
- fun onSelected(item: UserContribWikiSelectActivity.Item?)
- }
-
- private var item: UserContribWikiSelectActivity.Item? = null
- private var binding = ItemUserContribWikiSelectBinding.inflate(LayoutInflater.from(context), this)
- private val labelTypeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
- var callback: Callback? = null
-
- init {
- layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DimenUtil.roundedDpToPx(48f))
- setBackgroundResource(ResourceUtil.getThemedAttributeId(context, R.attr.selectableItemBackground))
- setOnClickListener {
- callback?.onSelected(item)
- }
- }
-
- fun setContents(item: UserContribWikiSelectActivity.Item, checked: Boolean) {
- this.item = item
- binding.wikiTitle.text = WikipediaApp.instance.languageState.getWikiLanguageName(item.itemCode)
- binding.itemCheck.isVisible = checked
- getTitleCodeFor(item.itemCode)?.let {
- binding.languageCode.text = it
- binding.languageCode.visibility = View.VISIBLE
- ViewUtil.formatLangButton(binding.languageCode, it, SearchFragment.LANG_BUTTON_TEXT_SIZE_SMALLER, SearchFragment.LANG_BUTTON_TEXT_SIZE_LARGER)
- } ?: run {
- binding.languageCode.visibility = View.GONE
- }
- item.imageRes?.let {
- ImageViewCompat.setImageTintList(binding.wikiLogo, ResourceUtil.getThemedColorStateList(context, R.attr.secondary_text_color))
- binding.wikiLogo.setImageResource(it)
- binding.wikiLogo.visibility = View.VISIBLE
- } ?: run {
- binding.wikiLogo.visibility = View.GONE
- }
- }
-
- fun setSingleLabel(text: String) {
- val accentColor = ResourceUtil.getThemedColorStateList(context, R.attr.colorAccent)
- binding.languageCode.visibility = View.GONE
- binding.wikiLogo.visibility = View.VISIBLE
- ImageViewCompat.setImageTintList(binding.wikiLogo, accentColor)
- binding.wikiLogo.setImageResource(R.drawable.ic_mode_edit_themed_24dp)
- binding.itemCheck.visibility = View.GONE
- binding.wikiTitle.setTextColor(accentColor)
- binding.wikiTitle.text = text.uppercase()
- binding.wikiTitle.typeface = labelTypeface
- binding.wikiTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
- }
-
- private fun getTitleCodeFor(itemCode: String): String? {
- return if (itemCode == Constants.WIKI_CODE_COMMONS || itemCode == Constants.WIKI_CODE_WIKIDATA) null
- else itemCode
- }
-}
diff --git a/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml b/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml
new file mode 100644
index 00000000000..619d4028bc1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_radio_button_checked_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_user_contrib.xml b/app/src/main/res/layout/activity_user_contrib.xml
index c4458da660a..89bcc2f7f06 100644
--- a/app/src/main/res/layout/activity_user_contrib.xml
+++ b/app/src/main/res/layout/activity_user_contrib.xml
@@ -32,43 +32,6 @@
android:textSize="20sp"
android:textStyle="bold"/>
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_user_contrib_wiki_select.xml b/app/src/main/res/layout/item_user_contrib_filter.xml
similarity index 92%
rename from app/src/main/res/layout/item_user_contrib_wiki_select.xml
rename to app/src/main/res/layout/item_user_contrib_filter.xml
index a056084623a..50bf191be3d 100644
--- a/app/src/main/res/layout/item_user_contrib_wiki_select.xml
+++ b/app/src/main/res/layout/item_user_contrib_filter.xml
@@ -19,17 +19,17 @@
android:textColor="?attr/secondary_text_color" />
+ app:tint="?attr/secondary_text_color" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index a7cc9ba21c5..f093ee50f18 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -1015,5 +1015,5 @@
বর্তমান
মিডিয়া চালনায় ত্রুটি৷
অবদান
- এতে অবদান:
+ এতে অবদান:
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 5c350cde307..14c825c2008 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -1277,5 +1277,5 @@
actual
Error de reproducció multimèdia
Contribucions
- Contribucions a:
+ Contribucions a:
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0af417da023..e4d43fae280 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1291,5 +1291,5 @@
aktuell
Fehler beim Abspielen von Medien.
Beiträge
- Beiträge zu:
+ Beiträge zu:
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 795a5ed1aee..b6964e26ec0 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1301,5 +1301,5 @@
actuel
Erreur lors de la lecture du média.
Contributions
- Contributions à :
+ Contributions à :
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 03677ee1b36..8ff913c7863 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -1247,5 +1247,5 @@
terkini
Terjadi kesalahan saat memutar media.
Kontribusi
- Kontribusi untuk:
+ Kontribusi untuk:
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 64796220b2b..c58b32bb4b1 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -1344,5 +1344,5 @@
נוכחי
שגיאה בנגינת מדיה.
תרומות
- תרומות ל־:
+ תרומות ל־:
diff --git a/app/src/main/res/values-ks/strings.xml b/app/src/main/res/values-ks/strings.xml
index 1473a016687..0ae255ce0fe 100644
--- a/app/src/main/res/values-ks/strings.xml
+++ b/app/src/main/res/values-ks/strings.xml
@@ -722,5 +722,5 @@
موجوٗدٕ گَردان
مِیٖڈیا چلاوٕنس منٛز چھےٚ خرٲبی.
شَرکٔژ
- کُن شَرکٲژٕ:
+ کُن شَرکٲژٕ:
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 3c45f2bb8cc..3b1ddbdaf61 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -1258,5 +1258,5 @@
тековна
Грешка при пуштањето на снимката.
Придонеси
- Придонеси на:
+ Придонеси на:
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 8cad2f17d9d..49585df19b9 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -1261,5 +1261,5 @@
huidige versie
Fout bij het afspelen van media.
Bijdragen
- Bijdragen aan:
+ Bijdragen aan:
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index fabb3e8878b..b988f432dcf 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -1289,5 +1289,5 @@
atual
Erro na reprodução de multimédia.
Contribuições
- Contribuições em:
+ Contribuições em:
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index a83cea839ba..f17a0980ff3 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -1274,5 +1274,5 @@
atual
Erro na reprodução de multimédia.
Contribuições
- Contribuições em:
+ Contribuições em:
diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml
index 28fb16e5f6f..40276985fb3 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -1282,5 +1282,6 @@
Label indicating that this contribution is the current revision of the article.\n{{identical|Current}}
Error shown when the selected media could not be played.
Menu label for accessing the contributions of the current user.
- Screen title for viewing the contributions of a user from a selected wiki.
+ Screen title for applying the filters for the contributions of a user.
+ Label for filter list that allows to filter contributions by namespace.
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 0708bcb1386..193201062db 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -1327,5 +1327,5 @@
trenutno
Napaka pri predvajanju predstavnosti.
Prispevki
- Prispevki k:
+ Prispevki k:
diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml
index 8d12b03b2f0..4ea43175192 100644
--- a/app/src/main/res/values-uz/strings.xml
+++ b/app/src/main/res/values-uz/strings.xml
@@ -1256,5 +1256,5 @@
joriy
Mediani ijro etishda xatolik yuz berdi.
Hissalar
- Qoʻshilgan hissa:
+ Qoʻshilgan hissa:
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 5a8721b55c1..db46797b4ff 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -138,7 +138,8 @@
readingFocusMode
editHistoryFilterType
talkTopicExpandAll
- userContribFilterNamespaces
+ userContribFilterNamespaces
+ userContribFilterLangCode
editSyntaxHighlight
editMonospaceFont
editLineNumbers
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b6cbe078388..1b0edf2eb73 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1340,11 +1340,12 @@
Contributions of %s
No archived pages found.
Filter by namespace
- All edits
+ All \"namespaces\"
+ Namespace filter
Article
current
Error playing media.
Contributions
- Contributions to:
+ Filter contributions