Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -41,6 +41,7 @@ import com.wireguard.android.model.Tunnel; | |||||
import com.wireguard.android.util.ExceptionLoggers; | import com.wireguard.android.util.ExceptionLoggers; | ||||
import com.wireguard.android.util.ObservableSortedKeyedList; | import com.wireguard.android.util.ObservableSortedKeyedList; | ||||
import com.wireguard.android.widget.MonkeyedSnackbar; | import com.wireguard.android.widget.MonkeyedSnackbar; | ||||
import com.wireguard.android.widget.MultiselectableRelativeLayout; | |||||
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; | import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; | ||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
@@ -263,30 +264,20 @@ public class TunnelListFragment extends BaseFragment { | |||||
super.onPause(); | super.onPause(); | ||||
} | } | ||||
private MultiselectableRelativeLayout viewForTunnel(final Tunnel tunnel, final List tunnels) { | |||||
return (MultiselectableRelativeLayout)binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(tunnel)).itemView; | |||||
} | |||||
@Override | @Override | ||||
public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { | public void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel, @Nullable final Tunnel newTunnel) { | ||||
if (binding == null) | if (binding == null) | ||||
return; | return; | ||||
Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { | Application.getTunnelManager().getTunnels().thenAccept(tunnels -> { | ||||
if (newTunnel != null) | if (newTunnel != null) | ||||
binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(newTunnel)).itemView.setActivated(true); | |||||
viewForTunnel(newTunnel, tunnels).setSingleSelected(true); | |||||
if (oldTunnel != null) | if (oldTunnel != null) | ||||
binding.tunnelList.findViewHolderForAdapterPosition(tunnels.indexOf(oldTunnel)).itemView.setActivated(false); | |||||
viewForTunnel(oldTunnel, tunnels).setSingleSelected(false); | |||||
}); | }); | ||||
/* Alternative 1: results in sluggish change: | |||||
if (binding.tunnelList.getAdapter() == null) | |||||
return; | |||||
* Alternative 2: results in overly quick change: | |||||
binding.tunnelList.getAdapter().notifyDataSetChanged(); | |||||
* Hence, we go with the above. | |||||
*/ | |||||
} | } | ||||
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) { | private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) { | ||||
@@ -373,10 +364,9 @@ public class TunnelListFragment extends BaseFragment { | |||||
}); | }); | ||||
if (actionMode != null) | if (actionMode != null) | ||||
binding.getRoot().setActivated(actionModeListener.checkedItems.contains(position)); | |||||
((MultiselectableRelativeLayout)binding.getRoot()).setMultiSelected(actionModeListener.checkedItems.contains(position)); | |||||
else | else | ||||
binding.getRoot().setActivated(getSelectedTunnel() == tunnel); | |||||
((MultiselectableRelativeLayout)binding.getRoot()).setSingleSelected(getSelectedTunnel() == tunnel); | |||||
}); | }); | ||||
} | } | ||||
@@ -433,7 +423,6 @@ public class TunnelListFragment extends BaseFragment { | |||||
public void onDestroyActionMode(final ActionMode mode) { | public void onDestroyActionMode(final ActionMode mode) { | ||||
actionMode = null; | actionMode = null; | ||||
resources = null; | resources = null; | ||||
checkedItems.clear(); | checkedItems.clear(); | ||||
binding.tunnelList.getAdapter().notifyDataSetChanged(); | binding.tunnelList.getAdapter().notifyDataSetChanged(); | ||||
} | } | ||||
@@ -0,0 +1,57 @@ | |||||
/* | |||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. | |||||
* SPDX-License-Identifier: Apache-2.0 | |||||
*/ | |||||
package com.wireguard.android.widget; | |||||
import android.content.Context; | |||||
import android.util.AttributeSet; | |||||
import android.widget.RelativeLayout; | |||||
import com.wireguard.android.R; | |||||
public class MultiselectableRelativeLayout extends RelativeLayout { | |||||
public MultiselectableRelativeLayout(final Context context) { | |||||
super(context); | |||||
} | |||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) { | |||||
super(context, attrs); | |||||
} | |||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) { | |||||
super(context, attrs, defStyleAttr); | |||||
} | |||||
public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { | |||||
super(context, attrs, defStyleAttr, defStyleRes); | |||||
} | |||||
private static final int[] STATE_MULTISELECTED = { R.attr.state_multiselected }; | |||||
private boolean multiselected; | |||||
@Override | |||||
protected int[] onCreateDrawableState(final int extraSpace) { | |||||
if (multiselected) { | |||||
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); | |||||
mergeDrawableStates(drawableState, STATE_MULTISELECTED); | |||||
return drawableState; | |||||
} | |||||
return super.onCreateDrawableState(extraSpace); | |||||
} | |||||
public void setMultiSelected(final boolean on) { | |||||
if (!multiselected) { | |||||
multiselected = true; | |||||
refreshDrawableState(); | |||||
} | |||||
setActivated(on); | |||||
} | |||||
public void setSingleSelected(final boolean on) { | |||||
if (multiselected) { | |||||
multiselected = false; | |||||
refreshDrawableState(); | |||||
} | |||||
setActivated(on); | |||||
} | |||||
} |
@@ -1,12 +1,13 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" | |||||
xmlns:app="http://schemas.android.com/apk/res-auto"> | |||||
<item> | <item> | ||||
<selector> | <selector> | ||||
<item android:state_activated="true"> | |||||
<item app:state_multiselected="true" android:state_activated="true"> | |||||
<color android:color="?android:attr/colorControlActivated" /> | |||||
</item> | |||||
<item app:state_multiselected="false" android:state_activated="true"> | |||||
<color android:color="?android:attr/colorControlHighlight" /> | <color android:color="?android:attr/colorControlHighlight" /> | ||||
<!-- TODO(msf): depending on whether or not we are in multiselect mode, choose instead: | |||||
<color android:color="?android:attr/colorControlActivated" /> | |||||
--> | |||||
</item> | </item> | ||||
</selector> | </selector> | ||||
</item> | </item> | ||||
@@ -25,7 +25,7 @@ | |||||
type="com.wireguard.android.fragment.TunnelListFragment" /> | type="com.wireguard.android.fragment.TunnelListFragment" /> | ||||
</data> | </data> | ||||
<RelativeLayout | |||||
<com.wireguard.android.widget.MultiselectableRelativeLayout | |||||
android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
android:background="@drawable/list_item_background" | android:background="@drawable/list_item_background" | ||||
@@ -51,5 +51,5 @@ | |||||
android:layout_alignParentEnd="true" | android:layout_alignParentEnd="true" | ||||
app:checked="@{item.state == State.UP}" | app:checked="@{item.state == State.UP}" | ||||
app:onBeforeCheckedChanged="@{fragment::setTunnelState}" /> | app:onBeforeCheckedChanged="@{fragment::setTunnelState}" /> | ||||
</RelativeLayout> | |||||
</com.wireguard.android.widget.MultiselectableRelativeLayout> | |||||
</layout> | </layout> |
@@ -0,0 +1,6 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<resources> | |||||
<declare-styleable name="Multiselected"> | |||||
<attr name="state_multiselected" format="boolean"/> | |||||
</declare-styleable> | |||||
</resources> |