diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt index bf4dc0cc0bcc8b7c788d35985966abbab0d33a65..600c78c7c2731c65d5ab4177324aefdb2cc72e52 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferListAdapter.kt @@ -30,11 +30,14 @@ class BufferListAdapter( liveData: LiveData<List<BufferProps>?>, runInBackground: (() -> Unit) -> Any, runOnUiThread: (Runnable) -> Any, - private val clickListener: ((BufferId) -> Unit)? = null + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null ) : RecyclerView.Adapter<BufferListAdapter.BufferViewHolder>() { var data = mutableListOf<BufferListItem>() - var collapsedNetworks = MutableLiveData<Set<NetworkId>>() + private val collapsedNetworks = MutableLiveData<Set<NetworkId>>() + + val selectedBuffers = MutableLiveData<Set<BufferId>>() fun expandListener(networkId: NetworkId) { if (collapsedNetworks.value.orEmpty().contains(networkId)) @@ -43,14 +46,29 @@ class BufferListAdapter( collapsedNetworks.postValue(collapsedNetworks.value.orEmpty() + networkId) } + fun toggleSelection(buffer: BufferId) { + val value = selectedBuffers.value.orEmpty() + if (value.contains(buffer)) { + selectedBuffers.value = value - buffer + } else { + selectedBuffers.value = value + buffer + } + } + + fun unselectAll() { + selectedBuffers.value = emptySet() + } + init { collapsedNetworks.value = emptySet() + selectedBuffers.value = emptySet() - liveData.zip(collapsedNetworks).observe( - lifecycleOwner, Observer { it: Pair<List<BufferProps>?, Set<NetworkId>>? -> + liveData.zip(collapsedNetworks, selectedBuffers).observe( + lifecycleOwner, Observer { it: Triple<List<BufferProps>?, Set<NetworkId>, Set<BufferId>>? -> runInBackground { val list = it?.first ?: emptyList() val collapsedNetworks = it?.second ?: emptySet() + val selected = it?.third ?: emptySet() val old: List<BufferListItem> = data val new: List<BufferListItem> = list.sortedBy { props -> @@ -61,7 +79,8 @@ class BufferListAdapter( BufferListItem( props, BufferState( - networkExpanded = !collapsedNetworks.contains(props.network.networkId) + networkExpanded = !collapsedNetworks.contains(props.network.networkId), + selected = selected.contains(props.info.bufferId) ) ) }.filter { (props, state) -> @@ -97,25 +116,29 @@ class BufferListAdapter( LayoutInflater.from(parent.context).inflate( R.layout.widget_buffer, parent, false ), - clickListener = clickListener + clickListener = clickListener, + longClickListener = longClickListener ) BufferInfo.Type.QueryBuffer.toInt() -> BufferViewHolder.QueryBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_buffer, parent, false ), - clickListener = clickListener + clickListener = clickListener, + longClickListener = longClickListener ) BufferInfo.Type.GroupBuffer.toInt() -> BufferViewHolder.GroupBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_buffer, parent, false ), - clickListener = clickListener + clickListener = clickListener, + longClickListener = longClickListener ) BufferInfo.Type.StatusBuffer.toInt() -> BufferViewHolder.StatusBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_network, parent, false ), clickListener = clickListener, + longClickListener = longClickListener, expansionListener = ::expandListener ) else -> throw IllegalArgumentException( @@ -146,7 +169,8 @@ class BufferListAdapter( ) data class BufferState( - val networkExpanded: Boolean + val networkExpanded: Boolean, + val selected: Boolean ) abstract class BufferViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -155,6 +179,7 @@ class BufferListAdapter( class StatusBuffer( itemView: View, private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null, private val expansionListener: ((NetworkId) -> Unit)? = null ) : BufferViewHolder(itemView) { @BindView(R.id.status) @@ -179,6 +204,16 @@ class BufferListAdapter( clickListener?.invoke(buffer) } + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + status.setOnClickListener { val network = networkId if (network != null) @@ -210,6 +245,8 @@ class BufferListAdapter( } ) + itemView.isSelected = state.selected + if (state.networkExpanded) { status.setImageDrawable(itemView.context.getCompatDrawable(R.drawable.ic_chevron_up)) } else { @@ -220,7 +257,8 @@ class BufferListAdapter( class GroupBuffer( itemView: View, - private val clickListener: ((BufferId) -> Unit)? = null + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null ) : BufferViewHolder(itemView) { @BindView(R.id.status) lateinit var status: ImageView @@ -249,6 +287,16 @@ class BufferListAdapter( clickListener?.invoke(buffer) } + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + online = itemView.context.getCompatDrawable(R.drawable.ic_status).mutate() offline = itemView.context.getCompatDrawable(R.drawable.ic_status_offline).mutate() @@ -282,6 +330,8 @@ class BufferListAdapter( } ) + itemView.isSelected = state.selected + description.visibleIf(props.description.isNotBlank()) status.setImageDrawable( @@ -295,7 +345,8 @@ class BufferListAdapter( class ChannelBuffer( itemView: View, - private val clickListener: ((BufferId) -> Unit)? = null + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null ) : BufferViewHolder(itemView) { @BindView(R.id.status) lateinit var status: ImageView @@ -324,6 +375,16 @@ class BufferListAdapter( clickListener?.invoke(buffer) } + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + online = itemView.context.getCompatDrawable(R.drawable.ic_status_channel).mutate() offline = itemView.context.getCompatDrawable(R.drawable.ic_status_channel_offline).mutate() @@ -357,6 +418,8 @@ class BufferListAdapter( } ) + itemView.isSelected = state.selected + description.visibleIf(props.description.isNotBlank()) status.setImageDrawable( @@ -370,7 +433,8 @@ class BufferListAdapter( class QueryBuffer( itemView: View, - private val clickListener: ((BufferId) -> Unit)? = null + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null ) : BufferViewHolder(itemView) { @BindView(R.id.status) lateinit var status: ImageView @@ -400,6 +464,16 @@ class BufferListAdapter( clickListener?.invoke(buffer) } + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + online = itemView.context.getCompatDrawable(R.drawable.ic_status).mutate() away = itemView.context.getCompatDrawable(R.drawable.ic_status).mutate() offline = itemView.context.getCompatDrawable(R.drawable.ic_status_offline).mutate() @@ -435,6 +509,8 @@ class BufferListAdapter( } ) + itemView.isSelected = state.selected + description.visibleIf(props.description.isNotBlank()) status.setImageDrawable( diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt index a1f72252016598ccc5c99ed320a7c0248500146d..6e3b95730ae2fd6cc413a3c3edf13d74859c7c72 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/buffers/BufferViewConfigFragment.kt @@ -3,9 +3,7 @@ package de.kuschku.quasseldroid_ng.ui.chat.buffers import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.widget.* -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.AdapterView import butterknife.BindView import butterknife.ButterKnife @@ -44,6 +42,30 @@ class BufferViewConfigFragment : ServiceBoundFragment() { private var ircFormatDeserializer: IrcFormatDeserializer? = null private lateinit var appearanceSettings: AppearanceSettings + private var isInActionMode = false + + private val actionModeCallback = object : ActionMode.Callback { + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + return true + } + + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + isInActionMode = true + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return false + } + + override fun onDestroyActionMode(mode: ActionMode?) { + isInActionMode = false + listAdapter.unselectAll() + } + } + + private lateinit var listAdapter: BufferListAdapter + override fun onCreate(savedInstanceState: Bundle?) { handlerThread.onCreate() super.onCreate(savedInstanceState) @@ -75,7 +97,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } } - chatList.adapter = BufferListAdapter( + listAdapter = BufferListAdapter( this, viewModel.bufferList.zip(database.filtered().listen(accountId)).map { val (data, activityList) = it @@ -107,8 +129,12 @@ class BufferViewConfigFragment : ServiceBoundFragment() { }, handlerThread::post, activity!!::runOnUiThread, - clickListener + clickListener, + longClickListener ) + chatList.adapter = listAdapter + + chatListToolbar.startActionMode(actionModeCallback) chatList.layoutManager = LinearLayoutManager(context) chatList.itemAnimator = DefaultItemAnimator() chatList.setItemViewCacheSize(10) @@ -121,6 +147,17 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } private val clickListener: ((BufferId) -> Unit)? = { - viewModel.setBuffer(it) + if (isInActionMode) { + longClickListener?.invoke(it) + } else { + viewModel.setBuffer(it) + } + } + + private val longClickListener: ((BufferId) -> Unit)? = { + if (!isInActionMode) { + chatListToolbar.startActionMode(actionModeCallback) + } + listAdapter.toggleSelection(it) } } diff --git a/app/src/main/res/drawable-v21/bg_menuitem_dark.xml b/app/src/main/res/drawable-v21/bg_menuitem_dark.xml new file mode 100644 index 0000000000000000000000000000000000000000..6ac08543def8f754c2b4df42f7dc62d4c8db842b --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_menuitem_dark.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_dark" /> + </shape> + </item> + <item> + <ripple android:color="@color/ripple_dark" /> + </item> +</selector> \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_menuitem_light.xml b/app/src/main/res/drawable-v21/bg_menuitem_light.xml new file mode 100644 index 0000000000000000000000000000000000000000..c86946bd169d81a7f324b141f34d99ae25af20e6 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_menuitem_light.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_light" /> + </shape> + </item> + <item> + <ripple android:color="@color/ripple_light" /> + </item> +</selector> \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_menuitem_dark.xml b/app/src/main/res/drawable/bg_menuitem_dark.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd43fb02f5f81c1e097a9f9a2cb5e9ab9eaf33f9 --- /dev/null +++ b/app/src/main/res/drawable/bg_menuitem_dark.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_dark" /> + </shape> + </item> + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_dark" /> + </shape> + </item> +</selector> \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_menuitem_light.xml b/app/src/main/res/drawable/bg_menuitem_light.xml new file mode 100644 index 0000000000000000000000000000000000000000..bdf40bd8ada5b31e3dfd5177616935965a837a02 --- /dev/null +++ b/app/src/main/res/drawable/bg_menuitem_light.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_light" /> + </shape> + </item> + <item android:state_pressed="true"> + <shape android:shape="rectangle"> + <solid android:color="@color/ripple_light" /> + </shape> + </item> +</selector> \ No newline at end of file diff --git a/app/src/main/res/layout/widget_buffer.xml b/app/src/main/res/layout/widget_buffer.xml index d181160f35ab0807698ebb607967154d133a5294..b162446dfbba5138df7462a7a61911c578821daa 100644 --- a/app/src/main/res/layout/widget_buffer.xml +++ b/app/src/main/res/layout/widget_buffer.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:foreground="?attr/selectableItemBackgroundBorderless" + android:background="?attr/backgroundMenuItem" android:minHeight="48dp" android:paddingBottom="8dp" android:paddingLeft="16dp" diff --git a/app/src/main/res/layout/widget_network.xml b/app/src/main/res/layout/widget_network.xml index 89af4b1c52a445d50db5290544a8f48f6b85554e..42128db09116daeae4c40bd2b3104846d0ecd474 100644 --- a/app/src/main/res/layout/widget_network.xml +++ b/app/src/main/res/layout/widget_network.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" + android:background="?attr/backgroundMenuItem" android:orientation="vertical"> <View diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index eeb7e7e311aa0f6dda78580cc847e80e23eea57e..2d2d7219d356f19028cfed58b868804443e3166f 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -71,4 +71,7 @@ <attr name="buttonTheme" format="reference" /> <attr name="buttonThemeColored" format="reference" /> <attr name="cardStyle" format="reference" /> + + <!-- Menu Items --> + <attr name="backgroundMenuItem" format="reference" /> </resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1b74be2c0c09054ae5886367863895f9eff7c065..77aae29ab1817632cc2526efa4dd877c09df684b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -12,4 +12,7 @@ <color name="colorAwayLight">#959595</color> <color name="colorAwayDark">#939393</color> + + <color name="ripple_dark">#33ffffff</color> + <color name="ripple_light">#1f000000</color> </resources> diff --git a/app/src/main/res/values/themes_base.xml b/app/src/main/res/values/themes_base.xml index 0506b7d5841c64a249dc60b7cd450d95e0bd3ea8..6a519ac9a936985367d680f6b73daf2a4f610934 100644 --- a/app/src/main/res/values/themes_base.xml +++ b/app/src/main/res/values/themes_base.xml @@ -4,22 +4,30 @@ <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> + + <item name="backgroundMenuItem">@drawable/bg_menuitem_dark</item> </style> <style name="Theme.AppTheme.Light" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> + + <item name="backgroundMenuItem">@drawable/bg_menuitem_light</item> </style> <style name="Theme.AppTheme.NoActionBar" parent="Theme.AppTheme"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> + + <item name="backgroundMenuItem">@drawable/bg_menuitem_dark</item> </style> <style name="Theme.AppTheme.Light.NoActionBar" parent="Theme.AppTheme.Light"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> + + <item name="backgroundMenuItem">@drawable/bg_menuitem_light</item> </style> <style name="Theme.Base.ChatTheme" parent="Theme.AppTheme.NoActionBar" /> @@ -38,6 +46,8 @@ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="actionBarPopupTheme">@style/Widget.PopupOverlay</item> + <item name="backgroundMenuItem">@drawable/bg_menuitem_dark</item> + <item name="windowActionModeOverlay">true</item> <item name="colorTextPrimary">#dedede</item> @@ -85,6 +95,8 @@ <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> <item name="actionBarPopupTheme">@style/Widget.PopupOverlay.Light</item> + <item name="backgroundMenuItem">@drawable/bg_menuitem_light</item> + <item name="windowActionModeOverlay">true</item> <item name="colorTextPrimary">#212121</item>