Merge pull request #3098 from nextcloud/issue-3072

Filter Conversations List for Unread/Mention
This commit is contained in:
Andy Scherzinger 2023-06-15 15:43:22 +02:00 committed by GitHub
commit 4dfa623c83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 347 additions and 6 deletions

View File

@ -303,7 +303,7 @@ dependencies {
implementation 'androidx.activity:activity-ktx:1.7.2'
implementation 'com.github.nextcloud.android-common:ui:0.10.0'
implementation 'com.github.nextcloud.android-common:ui:0.11.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:110.5481.0'
}

View File

@ -67,6 +67,7 @@ import coil.transform.CircleCropTransformation
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.CallActivity
@ -97,6 +98,7 @@ import com.nextcloud.talk.settings.SettingsActivity
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ClosedInterfaceImpl
@ -169,6 +171,7 @@ class ConversationsListActivity :
private var conversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var conversationItemsWithHeader: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private val searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var filterableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var searchItem: MenuItem? = null
private var chooseAccountItem: MenuItem? = null
private var searchView: SearchView? = null
@ -189,6 +192,11 @@ class ConversationsListActivity :
private var conversationsListBottomDialog: ConversationsListBottomDialog? = null
private var searchHelper: MessageSearchHelper? = null
private var searchViewDisposable: Disposable? = null
private var filterState =
mutableMapOf(
FilterConversationFragment.MENTION to false,
FilterConversationFragment.UNREAD to false
)
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
@ -413,7 +421,8 @@ class ConversationsListActivity :
searchItem!!.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
adapter!!.setHeadersShown(true)
adapter!!.updateDataSet(searchableConversationItems, false)
if (!filterState.containsValue(true)) filterableConversationItems = searchableConversationItems
adapter!!.updateDataSet(filterableConversationItems, false)
adapter!!.showAllHeaders()
binding?.swipeRefreshLayoutView?.isEnabled = false
return true
@ -421,7 +430,8 @@ class ConversationsListActivity :
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
adapter!!.setHeadersShown(false)
adapter!!.updateDataSet(conversationItems, false)
if (!filterState.containsValue(true)) filterableConversationItems = searchableConversationItems
adapter!!.updateDataSet(filterableConversationItems, false)
adapter!!.hideAllHeaders()
if (searchHelper != null) {
// cancel any pending searches
@ -758,6 +768,18 @@ class ConversationsListActivity :
}
}
updateFilterConversationButtonColor()
binding.filterConversationsButton.setOnClickListener {
val newFragment: DialogFragment = FilterConversationFragment.newInstance(
adapter!!,
conversationItems,
filterState,
this
)
newFragment.show(supportFragmentManager, FilterConversationFragment.TAG)
}
binding?.newMentionPopupBubble?.hide()
binding?.newMentionPopupBubble?.setPopupBubbleListener {
binding?.recyclerView?.smoothScrollToPosition(
@ -1456,6 +1478,26 @@ class ConversationsListActivity :
showErrorDialog()
}
fun updateFilterState(mention: Boolean, unread: Boolean) {
filterState[FilterConversationFragment.MENTION] = mention
filterState[FilterConversationFragment.UNREAD] = unread
}
fun setFilterableItems(items: MutableList<AbstractFlexibleItem<*>>) { filterableConversationItems = items }
fun updateFilterConversationButtonColor() {
if (filterState.containsValue(true)) {
binding.filterConversationsButton.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
} else {
binding.filterConversationsButton.let {
viewThemeUtils.platform.colorImageView(
it,
ColorRole.ON_SURFACE_VARIANT
)
}
}
}
companion object {
const val TAG = "ConvListController"
const val UNREAD_BUBBLE_DELAY = 2500

View File

@ -0,0 +1,169 @@
/*
* Nextcloud Talk application
*
* @author Julius Linus
* Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.ui.dialog
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.items.ConversationItem
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.databinding.DialogFilterConversationBinding
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class FilterConversationFragment(
adapter: FlexibleAdapter<AbstractFlexibleItem<*>>,
currentConversations: MutableList<AbstractFlexibleItem<*>>,
savedFilterState: MutableMap<String, Boolean>,
conversationsListActivity: ConversationsListActivity
) : DialogFragment() {
lateinit var binding: DialogFilterConversationBinding
private var dialogView: View? = null
private var currentAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> = adapter
private var currentItems = currentConversations
private var filterState = savedFilterState
private var conversationsList = conversationsListActivity
@Inject
lateinit var userManager: UserManager
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogFilterConversationBinding.inflate(LayoutInflater.from(context))
dialogView = binding.root
return MaterialAlertDialogBuilder(requireContext()).setView(dialogView).create()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
setUpColors()
setUpListeners()
return inflater.inflate(R.layout.dialog_filter_conversation, container, false)
}
private fun setUpColors() {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(binding.buttonClose)
binding.run {
listOf(
binding.root
)
}.forEach(viewThemeUtils.platform::colorViewBackground)
binding.run {
listOf(
unreadFilterChip,
mentionedFilterChip
)
}.forEach(viewThemeUtils.material::themeChipFilter)
setUpChips()
}
private fun setUpListeners() {
binding.unreadFilterChip.setOnCheckedChangeListener { _, isChecked ->
filterState[UNREAD] = isChecked
binding.unreadFilterChip.isChecked = isChecked
processSubmit()
}
binding.mentionedFilterChip.setOnCheckedChangeListener { _, isChecked ->
filterState[MENTION] = isChecked
binding.mentionedFilterChip.isChecked = isChecked
processSubmit()
}
binding.buttonClose.setOnClickListener {
dismiss()
}
}
private fun setUpChips() {
binding.unreadFilterChip.isChecked = filterState[UNREAD]!!
binding.mentionedFilterChip.isChecked = filterState[MENTION]!!
}
private fun processSubmit() {
val newItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
if (!filterState.containsValue(true)) {
currentAdapter.updateDataSet(currentItems, true)
} else {
val items = currentItems
for (i in items) {
val conversation = (i as ConversationItem).model
if (filter(conversation)) {
newItems.add(i)
}
}
currentAdapter.updateDataSet(newItems, true)
conversationsList.setFilterableItems(newItems)
}
conversationsList.updateFilterState(
filterState[MENTION]!!,
filterState[UNREAD]!!
)
conversationsList.updateFilterConversationButtonColor()
}
private fun filter(conversation: Conversation): Boolean {
var result = true
for ((k, v) in filterState) {
if (v) {
when (k) {
MENTION -> result = result && conversation.unreadMention
UNREAD -> result = result && (conversation.unreadMessages > 0)
}
}
}
return result
}
companion object {
@JvmStatic
fun newInstance(
adapter: FlexibleAdapter<AbstractFlexibleItem<*>>,
currentConversations: MutableList<AbstractFlexibleItem<*>>,
savedFilterState: MutableMap<String, Boolean>,
conversationsListActivity: ConversationsListActivity
) = FilterConversationFragment(adapter, currentConversations, savedFilterState, conversationsListActivity)
val TAG: String = FilterConversationFragment::class.java.simpleName
const val MENTION: String = "mention"
const val UNREAD: String = "unread"
}
}

View File

@ -0,0 +1,26 @@
<!--
@author Google LLC
Copyright (C) 2023 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
</vector>

View File

@ -83,10 +83,22 @@
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/menu_button"
app:layout_constraintEnd_toStartOf="@id/rightContainer"
app:layout_constraintEnd_toStartOf="@id/filter_conversations_button"
app:layout_constraintTop_toTopOf="parent"
tools:text="Search in Nextcloud" />
<ImageView
android:id="@+id/filter_conversations_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:contentDescription="@string/nc_filter"
android:src="@drawable/ic_baseline_filter_list_24"
app:layout_constraintBaseline_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rightContainer"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/rightContainer"
android:layout_width="wrap_content"

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Julius Linus
~ Copyright (C) 2023 Julius Linus <julius.linus@nextcloud.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/white"
tools:visibility="visible">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_margin"
android:text="@string/nc_filter"
android:textSize="@dimen/md_title_textsize" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.material.chip.ChipGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginTop="@dimen/standard_half_margin"
android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_half_margin"
app:chipSpacingHorizontal="@dimen/standard_margin">
<com.google.android.material.chip.Chip
android:id="@+id/unread_filter_chip"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/unread" />
<com.google.android.material.chip.Chip
android:id="@+id/mentioned_filter_chip"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/mentioned" />
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_half_margin"
android:gravity="end"
android:orientation="horizontal"
android:paddingStart="@dimen/dialog_padding"
android:paddingEnd="@dimen/dialog_padding"
android:paddingBottom="@dimen/dialog_padding_top_bottom">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_close"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_size_clickable_area"
android:text="@string/close" />
</LinearLayout>
</LinearLayout>

View File

@ -63,7 +63,6 @@
<color name="nc_darkGreen">#006400</color>
<color name="controller_chat_separator">#E8E8E8</color>
<color name="grey_600">#757575</color>
<color name="grey_900">#212121</color>
<color name="nc_grey">#D5D5D5</color>
<color name="controller_call_incomingCallTextView">#E9FFFFFF</color>
<color name="grey950">#111111</color>

View File

@ -680,5 +680,8 @@ How to translate with transifex:
<string name="translation_error_title">Translation failed</string>
<string name="translation_error_message">Could not detect language</string>
<string name="translation_copy_translated_text">Copy translated text</string>
<string name="nc_filter">Filter Conversations</string>
<string name="mentioned">Mentioned</string>
<string name="unread">Unread</string>
</resources>

View File

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 108 warnings</span>
<span class="mdl-layout-title">Lint Report: 107 warnings</span>