From a448f5071e1eeebc9ab870c25b1f5a4f4ea7e8fd Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 25 Feb 2018 02:16:08 +0100 Subject: [PATCH] Allow contextual buffer actions --- .../ui/chat/buffers/BufferListAdapter.kt | 22 ++-- .../chat/buffers/BufferViewConfigFragment.kt | 120 +++++++++++++++++- .../ui/viewmodel/QuasselViewModel.kt | 41 +++++- app/src/main/res/menu/context_buffer.xml | 19 +++ app/src/main/res/values/strings.xml | 11 +- .../libquassel/quassel/syncables/Network.kt | 1 + 6 files changed, 191 insertions(+), 23 deletions(-) create mode 100644 app/src/main/res/menu/context_buffer.xml 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 600c78c7c..19cf99251 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 @@ -28,6 +28,7 @@ import de.kuschku.quasseldroid_ng.util.helper.zip class BufferListAdapter( lifecycleOwner: LifecycleOwner, liveData: LiveData<List<BufferProps>?>, + private val selectedBuffer: MutableLiveData<BufferId>, runInBackground: (() -> Unit) -> Any, runOnUiThread: (Runnable) -> Any, private val clickListener: ((BufferId) -> Unit)? = null, @@ -37,8 +38,6 @@ class BufferListAdapter( private val collapsedNetworks = MutableLiveData<Set<NetworkId>>() - val selectedBuffers = MutableLiveData<Set<BufferId>>() - fun expandListener(networkId: NetworkId) { if (collapsedNetworks.value.orEmpty().contains(networkId)) collapsedNetworks.postValue(collapsedNetworks.value.orEmpty() - networkId) @@ -47,28 +46,27 @@ class BufferListAdapter( } fun toggleSelection(buffer: BufferId) { - val value = selectedBuffers.value.orEmpty() - if (value.contains(buffer)) { - selectedBuffers.value = value - buffer + if (selectedBuffer.value == buffer) { + selectedBuffer.value = -1 } else { - selectedBuffers.value = value + buffer + selectedBuffer.value = buffer } } fun unselectAll() { - selectedBuffers.value = emptySet() + selectedBuffer.value = -1 } init { collapsedNetworks.value = emptySet() - selectedBuffers.value = emptySet() + selectedBuffer.value = -1 - liveData.zip(collapsedNetworks, selectedBuffers).observe( - lifecycleOwner, Observer { it: Triple<List<BufferProps>?, Set<NetworkId>, Set<BufferId>>? -> + liveData.zip(collapsedNetworks, selectedBuffer).observe( + lifecycleOwner, Observer { it: Triple<List<BufferProps>?, Set<NetworkId>, BufferId>? -> runInBackground { val list = it?.first ?: emptyList() val collapsedNetworks = it?.second ?: emptySet() - val selected = it?.third ?: emptySet() + val selected = it?.third ?: -1 val old: List<BufferListItem> = data val new: List<BufferListItem> = list.sortedBy { props -> @@ -80,7 +78,7 @@ class BufferListAdapter( props, BufferState( networkExpanded = !collapsedNetworks.contains(props.network.networkId), - selected = selected.contains(props.info.bufferId) + selected = selected == props.info.bufferId ) ) }.filter { (props, state) -> 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 6e3b95730..a4a5ab9a5 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 @@ -1,5 +1,6 @@ package de.kuschku.quasseldroid_ng.ui.chat.buffers +import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.widget.* @@ -7,10 +8,13 @@ import android.view.* import android.widget.AdapterView import butterknife.BindView import butterknife.ButterKnife +import com.afollestad.materialdialogs.MaterialDialog import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Buffer_Activity import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork import de.kuschku.libquassel.util.hasFlag import de.kuschku.libquassel.util.minus import de.kuschku.quasseldroid_ng.R @@ -42,15 +46,63 @@ class BufferViewConfigFragment : ServiceBoundFragment() { private var ircFormatDeserializer: IrcFormatDeserializer? = null private lateinit var appearanceSettings: AppearanceSettings - private var isInActionMode = false + private var actionMode: ActionMode? = null private val actionModeCallback = object : ActionMode.Callback { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { - return true + val selected = viewModel.selectedBuffer.value + val info = selected?.info + val session = viewModel.session.value + val network = session?.networks?.get(selected?.info?.networkId) + + return if (info != null && session != null) { + when (item?.itemId) { + R.id.action_connect -> { + network?.requestConnect() + actionMode?.finish() + true + } + R.id.action_disconnect -> { + network?.requestDisconnect() + actionMode?.finish() + true + } + R.id.action_join -> { + session.rpcHandler?.sendInput(info, "/join ${info.bufferName}") + actionMode?.finish() + true + } + R.id.action_part -> { + session.rpcHandler?.sendInput(info, "/part ${info.bufferName}") + actionMode?.finish() + true + } + R.id.action_delete -> { + MaterialDialog.Builder(activity!!) + .content(R.string.buffer_delete_confirmation) + .positiveText(R.string.label_yes) + .negativeText(R.string.label_no) + .negativeColorAttr(R.attr.colorTextPrimary) + .backgroundColorAttr(R.attr.colorBackgroundCard) + .contentColorAttr(R.attr.colorTextPrimary) + .onPositive { _, _ -> + session.bufferSyncer?.requestRemoveBuffer(selected.info.bufferId) + } + .build() + .show() + actionMode?.finish() + true + } + else -> false + } + } else { + false + } } override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { - isInActionMode = true + actionMode = mode + mode?.menuInflater?.inflate(R.menu.context_buffer, menu) return true } @@ -59,7 +111,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } override fun onDestroyActionMode(mode: ActionMode?) { - isInActionMode = false + actionMode = null listAdapter.unselectAll() } } @@ -127,6 +179,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { props.info.type.hasFlag(Buffer_Type.StatusBuffer) } }, + viewModel.selectedBufferId, handlerThread::post, activity!!::runOnUiThread, clickListener, @@ -134,6 +187,55 @@ class BufferViewConfigFragment : ServiceBoundFragment() { ) chatList.adapter = listAdapter + viewModel.selectedBuffer.observe(this, Observer { buffer -> + if (buffer != null) { + val menu = actionMode?.menu + if (menu != null) { + val allActions = setOf( + R.id.action_connect, + R.id.action_disconnect, + R.id.action_join, + R.id.action_part, + R.id.action_delete + ) + + val availableActions = when (buffer.info?.type?.enabledValues()?.firstOrNull()) { + Buffer_Type.StatusBuffer -> { + when (buffer.connectionState) { + INetwork.ConnectionState.Disconnected -> setOf(R.id.action_connect) + INetwork.ConnectionState.Initialized -> setOf(R.id.action_disconnect) + else -> setOf( + R.id.action_connect, R.id.action_disconnect + ) + } + } + Buffer_Type.ChannelBuffer -> { + if (buffer.joined) { + setOf(R.id.action_part) + } else { + setOf(R.id.action_join, R.id.action_delete) + } + } + Buffer_Type.QueryBuffer -> { + setOf(R.id.action_delete) + } + else -> emptySet() + } + + val unavailableActions = allActions - availableActions + + for (action in availableActions) { + menu.findItem(action)?.isVisible = true + } + for (action in unavailableActions) { + menu.findItem(action)?.isVisible = false + } + } + } else { + actionMode?.finish() + } + }) + chatListToolbar.startActionMode(actionModeCallback) chatList.layoutManager = LinearLayoutManager(context) chatList.itemAnimator = DefaultItemAnimator() @@ -147,7 +249,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } private val clickListener: ((BufferId) -> Unit)? = { - if (isInActionMode) { + if (actionMode != null) { longClickListener?.invoke(it) } else { viewModel.setBuffer(it) @@ -155,9 +257,15 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } private val longClickListener: ((BufferId) -> Unit)? = { - if (!isInActionMode) { + if (actionMode == null) { chatListToolbar.startActionMode(actionModeCallback) } listAdapter.toggleSelection(it) } + + data class SelectedItem( + val info: BufferInfo? = null, + val connectionState: INetwork.ConnectionState = INetwork.ConnectionState.Disconnected, + val joined: Boolean = false + ) } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt index d1214a1e6..eff40a23f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt @@ -17,6 +17,7 @@ import de.kuschku.libquassel.util.hasFlag import de.kuschku.quasseldroid_ng.ui.chat.NickListAdapter import de.kuschku.quasseldroid_ng.ui.chat.ToolbarFragment import de.kuschku.quasseldroid_ng.ui.chat.buffers.BufferListAdapter +import de.kuschku.quasseldroid_ng.ui.chat.buffers.BufferViewConfigFragment import de.kuschku.quasseldroid_ng.util.helper.map import de.kuschku.quasseldroid_ng.util.helper.switchMap import de.kuschku.quasseldroid_ng.util.helper.switchMapRx @@ -191,6 +192,40 @@ class QuasselViewModel : ViewModel() { } } + val selectedBufferId = MutableLiveData<BufferId>() + val selectedBuffer = session.zip(selectedBufferId).switchMapRx { (session, buffer) -> + val bufferSyncer = session?.bufferSyncer + if (bufferSyncer != null) { + val info = bufferSyncer.bufferInfo(buffer) + if (info != null) { + val network = session.networks[info.networkId] + when (info.type.enabledValues().firstOrNull()) { + Buffer_Type.StatusBuffer -> { + network?.liveConnectionState?.map { + BufferViewConfigFragment.SelectedItem( + info, + connectionState = it + ) + } + } + Buffer_Type.ChannelBuffer -> { + network?.liveIrcChannel(info.bufferName)?.map { + BufferViewConfigFragment.SelectedItem( + info, + joined = it != IrcChannel.NULL + ) + } + } + else -> Observable.just(BufferViewConfigFragment.SelectedItem(info)) + } + } else { + Observable.just(BufferViewConfigFragment.SelectedItem(info)) + } + } else { + Observable.just(BufferViewConfigFragment.SelectedItem()) + } + } + val bufferList: LiveData<Pair<BufferViewConfig?, List<BufferListAdapter.BufferProps>>?> = session.zip( bufferViewConfig ).switchMapRx { (session, config) -> @@ -221,7 +256,7 @@ class QuasselViewModel : ViewModel() { } }.switchMap { (activity, highlights) -> when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { + BufferInfo.Type.QueryBuffer.toInt() -> { network.liveIrcUser(info.bufferName).switchMap { user -> user.live_away.switchMap { away -> user.live_realName.map { realName -> @@ -260,7 +295,7 @@ class QuasselViewModel : ViewModel() { } } } - BufferInfo.Type.StatusBuffer.toInt() -> { + BufferInfo.Type.StatusBuffer.toInt() -> { network.liveConnectionState.map { BufferListAdapter.BufferProps( info = info, @@ -272,7 +307,7 @@ class QuasselViewModel : ViewModel() { ) } } - else -> Observable.just( + else -> Observable.just( BufferListAdapter.BufferProps( info = info, network = network.networkInfo(), diff --git a/app/src/main/res/menu/context_buffer.xml b/app/src/main/res/menu/context_buffer.xml new file mode 100644 index 000000000..08152e25c --- /dev/null +++ b/app/src/main/res/menu/context_buffer.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/action_connect" + android:title="@string/label_connect" /> + <item + android:id="@+id/action_disconnect" + android:title="@string/label_disconnect" /> + <item + android:id="@+id/action_join" + android:title="@string/label_join" /> + <item + android:id="@+id/action_part" + android:title="@string/label_part" /> + <item + android:id="@+id/action_delete" + android:title="@string/label_delete" /> + <!--<item android:title="Merge" />--> +</menu> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 62902007f..dd5bb3719 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,12 +6,15 @@ <string name="label_cancel">Cancel</string> <string name="label_clear_backlog">Clear Backlog</string> + <string name="label_close">Close</string> + <string name="label_connect">Connect</string> <string name="label_delete">Delete</string> <string name="label_disconnect">Disconnect</string> - <string name="label_close">Close</string> - <string name="label_open">Open</string> <string name="label_filter_messages">Filter Messages</string> <string name="label_input_history">Input History</string> + <string name="label_join">Join</string> + <string name="label_open">Open</string> + <string name="label_part">Part</string> <string name="label_placeholder">Write a messageā¦</string> <string name="label_save">Save</string> <string name="label_select_multiple">Select</string> @@ -27,4 +30,8 @@ <string name="notification_channel_background_title">Background</string> <string name="notification_channel_highlight" translatable="false">highlight</string> <string name="notification_channel_highlight_title">Highlight</string> + + <string name="buffer_delete_confirmation">Do you want to delete this buffer permanently?</string> + <string name="label_yes">Yes</string> + <string name="label_no">No</string> </resources> diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt index 680286716..246741dd4 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt @@ -434,6 +434,7 @@ class Network constructor( if (_connectionState == actualConnectionState) return _connectionState = actualConnectionState + liveConnectionState.onNext(_connectionState) super.setConnectionState(state) } -- GitLab