From f706fcde4ae1e0521cc65a2b08e04f50264f72c6 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Sun, 5 Jan 2020 17:27:39 +0200 Subject: [PATCH] Updated the recycler view library. Added a simple realization of 'recyclerview-selection' library.| #793 --- FlowCrypt/build.gradle | 3 +- .../email/ui/activity/EmailManagerActivity.kt | 2 +- .../ui/activity/fragment/EmailListFragment.kt | 91 ++++++++++++++++++- .../email/ui/adapter/MsgsPagedListAdapter.kt | 19 ++++ .../adapter/selection/MsgItemDetailsLookup.kt | 25 +++++ .../main/res/drawable/selector_msg_item.xml | 8 ++ .../main/res/layout/messages_list_item.xml | 3 +- .../res/menu/message_list_context_menu.xml | 16 +++- ext.gradle | 2 + 9 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/selection/MsgItemDetailsLookup.kt create mode 100644 FlowCrypt/src/main/res/drawable/selector_msg_item.xml diff --git a/FlowCrypt/build.gradle b/FlowCrypt/build.gradle index 335584583e..397c79e4a7 100644 --- a/FlowCrypt/build.gradle +++ b/FlowCrypt/build.gradle @@ -350,7 +350,8 @@ dependencies { implementation "androidx.legacy:legacy-preference-v14:${rootProject.ext.androidxBaseVersion}" implementation "androidx.cardview:cardview:${rootProject.ext.androidxBaseVersion}" implementation "androidx.browser:browser:${rootProject.ext.androidxBaseVersion}" - implementation "androidx.recyclerview:recyclerview:${rootProject.ext.androidxBaseVersion}" + implementation "androidx.recyclerview:recyclerview:${rootProject.ext.recyclerViewVersion}" + implementation "androidx.recyclerview:recyclerview-selection:${rootProject.ext.recyclerViewVersion}-beta01" implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.test.espresso:espresso-idling-resource:${rootProject.ext.espressoVersion}" implementation "androidx.lifecycle:lifecycle-extensions:${rootProject.ext.lifecycleVersion}" diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt index ecf42e30d3..c353d6184d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/EmailManagerActivity.kt @@ -559,7 +559,7 @@ class EmailManagerActivity : BaseEmailListActivity(), NavigationView.OnNavigatio val fragment = supportFragmentManager .findFragmentById(R.id.emailListFragment) as EmailListFragment? - //fragment?.onDrawerStateChanged(isOpen) + fragment?.onDrawerStateChanged(isOpen) } private fun initViews() { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt index cc7ff62caa..a2f2d81fed 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/EmailListFragment.kt @@ -9,14 +9,21 @@ import android.content.Context import android.os.Bundle import android.text.TextUtils import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.paging.PagedList +import androidx.recyclerview.selection.SelectionTracker +import androidx.recyclerview.selection.StableIdKeyProvider +import androidx.recyclerview.selection.StorageStrategy import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -34,6 +41,7 @@ import com.flowcrypt.email.ui.activity.fragment.base.BaseSyncFragment import com.flowcrypt.email.ui.activity.fragment.dialog.InfoDialogFragment import com.flowcrypt.email.ui.activity.fragment.dialog.TwoWayDialogFragment import com.flowcrypt.email.ui.adapter.MsgsPagedListAdapter +import com.flowcrypt.email.ui.adapter.selection.MsgItemDetailsLookup import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.UIUtil import com.flowcrypt.email.util.idling.SingleIdlingResources @@ -60,6 +68,8 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen private var swipeRefreshLayout: SwipeRefreshLayout? = null private var textViewActionProgress: TextView? = null private var progressBarActionProgress: ProgressBar? = null + private var tracker: SelectionTracker? = null + private var actionMode: ActionMode? = null private lateinit var adapter: MsgsPagedListAdapter private lateinit var messagesViewModel: MessagesViewModel @@ -107,6 +117,26 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen } } + private val selectionObserver = object : SelectionTracker.SelectionObserver() { + override fun onSelectionChanged() { + super.onSelectionChanged() + when { + tracker?.hasSelection() == true -> { + if (actionMode == null) { + actionMode = (this@EmailListFragment.activity as AppCompatActivity) + .startSupportActionMode(genActionModeForMsgs()) + } + actionMode?.title = "${tracker?.selection?.size()}" + } + + tracker?.hasSelection() == false -> { + actionMode?.finish() + actionMode = null + } + } + } + } + override val contentView: View? get() = recyclerViewMsgs @@ -290,6 +320,10 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen } override fun onMsgClick(msgEntity: MessageEntity) { + if (tracker?.hasSelection() == true) { + return + } + val isOutbox = JavaEmailConstants.FOLDER_OUTBOX.equals(listener?.currentFolder?.fullName, ignoreCase = true) val isRawMsgAvailable = msgEntity.rawMessageWithoutAttachments?.isNotEmpty() if (isOutbox || isRawMsgAvailable == true || GeneralUtil.isConnected(context)) { @@ -315,6 +349,15 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen onFolderChanged(deleteAllMsgs = true) } + fun onDrawerStateChanged(isOpen: Boolean) { + //todo-denbond7 #793 need to fix that + /*if (isOpen) { + actionMode?.finish() + } else { + actionMode?.invalidate() + }*/ + } + /** * Reload the folder messages. */ @@ -439,10 +482,7 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen progressBarActionProgress = view.findViewById(R.id.progressBarActionProgress) recyclerViewMsgs = view.findViewById(R.id.recyclerViewMsgs) - val layoutManager = LinearLayoutManager(context) - recyclerViewMsgs?.layoutManager = layoutManager - recyclerViewMsgs?.addItemDecoration(DividerItemDecoration(context, layoutManager.orientation)) - recyclerViewMsgs?.adapter = adapter + setupRecyclerView() footerProgressView = LayoutInflater.from(context).inflate(R.layout.list_view_progress_footer, recyclerViewMsgs, false) footerProgressView?.visibility = View.GONE @@ -453,6 +493,49 @@ class EmailListFragment : BaseSyncFragment(), SwipeRefreshLayout.OnRefreshListen swipeRefreshLayout!!.setOnRefreshListener(this) } + private fun setupRecyclerView() { + val layoutManager = LinearLayoutManager(context) + recyclerViewMsgs?.layoutManager = layoutManager + recyclerViewMsgs?.addItemDecoration(DividerItemDecoration(context, layoutManager.orientation)) + recyclerViewMsgs?.adapter = adapter + + adapter.tracker = null + recyclerViewMsgs?.let { + tracker = SelectionTracker.Builder( + EmailListFragment::class.java.simpleName, + it, + StableIdKeyProvider(it), + MsgItemDetailsLookup(it), + StorageStrategy.createLongStorage() + ).build() + tracker?.addObserver(selectionObserver) + adapter.tracker = tracker + } + } + + private fun genActionModeForMsgs(): ActionMode.Callback { + return object : ActionMode.Callback { + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + when (item?.itemId) { + else -> return false + } + } + + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + mode?.menuInflater?.inflate(R.menu.message_list_context_menu, menu) + swipeRefreshLayout?.isEnabled = false + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean = true + + override fun onDestroyActionMode(mode: ActionMode?) { + swipeRefreshLayout?.isEnabled = true + tracker?.clearSelection() + } + } + } + interface OnManageEmailsListener { val currentAccountDao: AccountDao? diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/MsgsPagedListAdapter.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/MsgsPagedListAdapter.kt index 901819954e..26d76ac1de 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/MsgsPagedListAdapter.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/MsgsPagedListAdapter.kt @@ -21,6 +21,8 @@ import android.widget.TextView import androidx.annotation.IntDef import androidx.core.content.ContextCompat import androidx.paging.PagedListAdapter +import androidx.recyclerview.selection.ItemDetailsLookup +import androidx.recyclerview.selection.SelectionTracker import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.flowcrypt.email.R @@ -46,9 +48,11 @@ class MsgsPagedListAdapter(private val onMessageClickListener: OnMessageClickLis PagedListAdapter(DIFF_CALLBACK) { private val senderNamePattern: Pattern private var isFooterEnabled = false + var tracker: SelectionTracker? = null init { this.senderNamePattern = prepareSenderNamePattern() + setHasStableIds(true) } override fun onCreateViewHolder(parent: ViewGroup, @ItemType viewType: Int): BaseViewHolder { @@ -68,6 +72,8 @@ class MsgsPagedListAdapter(private val onMessageClickListener: OnMessageClickLis } override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + holder.setActivatedState(tracker?.isSelected(getItem(position)?.id) ?: false) + when (holder.itemType) { MESSAGE -> { val msgEntity = getItem(position) @@ -98,6 +104,10 @@ class MsgsPagedListAdapter(private val onMessageClickListener: OnMessageClickLis return currentItemCount + if (isFooterEnabled && currentItemCount > 0) 1 else 0 } + override fun getItemId(position: Int): Long { + return getItem(position)?.id ?: super.getItemId(position) + } + private fun updateItem(messageEntity: MessageEntity?, viewHolder: MessageViewHolder) { val context = viewHolder.itemView.context if (messageEntity != null) { @@ -352,6 +362,15 @@ class MsgsPagedListAdapter(private val onMessageClickListener: OnMessageClickLis abstract inner class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @ItemType abstract val itemType: Int + + fun getItemDetails(): ItemDetailsLookup.ItemDetails = object : ItemDetailsLookup.ItemDetails() { + override fun getPosition(): Int = adapterPosition + override fun getSelectionKey(): Long? = itemId + } + + fun setActivatedState(isActivated: Boolean) { + itemView.isActivated = isActivated + } } inner class MessageViewHolder(itemView: View) : BaseViewHolder(itemView) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/selection/MsgItemDetailsLookup.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/selection/MsgItemDetailsLookup.kt new file mode 100644 index 0000000000..952709febb --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/adapter/selection/MsgItemDetailsLookup.kt @@ -0,0 +1,25 @@ +/* + * © 2016-2020 FlowCrypt Limited. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui.adapter.selection + +import android.view.MotionEvent +import androidx.recyclerview.selection.ItemDetailsLookup +import androidx.recyclerview.widget.RecyclerView +import com.flowcrypt.email.ui.adapter.MsgsPagedListAdapter + +/** + * @author Denis Bondarenko + * Date: 1/5/20 + * Time: 2:48 PM + * E-mail: DenBond7@gmail.com + */ +class MsgItemDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() { + override fun getItemDetails(e: MotionEvent): ItemDetails? { + return recyclerView.findChildViewUnder(e.x, e.y)?.let { + (recyclerView.getChildViewHolder(it) as? MsgsPagedListAdapter.BaseViewHolder)?.getItemDetails() + } + } +} \ No newline at end of file diff --git a/FlowCrypt/src/main/res/drawable/selector_msg_item.xml b/FlowCrypt/src/main/res/drawable/selector_msg_item.xml new file mode 100644 index 0000000000..0dcacae9ed --- /dev/null +++ b/FlowCrypt/src/main/res/drawable/selector_msg_item.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/FlowCrypt/src/main/res/layout/messages_list_item.xml b/FlowCrypt/src/main/res/layout/messages_list_item.xml index 74279c1782..93dde25193 100644 --- a/FlowCrypt/src/main/res/layout/messages_list_item.xml +++ b/FlowCrypt/src/main/res/layout/messages_list_item.xml @@ -8,7 +8,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="@dimen/layout_height_message_item" - android:background="?attr/selectableItemBackground" + android:background="@drawable/selector_msg_item" + android:foreground="?attr/selectableItemBackground" android:orientation="vertical"> + + + app:showAsAction="always" /> + + \ No newline at end of file diff --git a/ext.gradle b/ext.gradle index 8f5533a0cd..cc94b178fe 100644 --- a/ext.gradle +++ b/ext.gradle @@ -15,6 +15,8 @@ ext { javaSourceCompatibility = '1.7' /*https://developer.android.com/jetpack/androidx/androidx-rn*/ androidxBaseVersion = '1.0.0' + /* https://developer.android.com/jetpack/androidx/releases/recyclerview*/ + recyclerViewVersion = '1.1.0' /* https://developer.android.com/jetpack/androidx/releases/lifecycle*/ lifecycleVersion = '2.2.0-rc01' /* https://developer.android.com/jetpack/androidx/releases/room#declaring_dependencies*/