Switch from ListView to RecyclerView
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
2c7203ab8d
commit
b37b48b8dc
@ -13,10 +13,10 @@ import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.InputFilter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
import com.wireguard.android.widget.ToggleSwitch;
|
||||
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
|
||||
@ -67,37 +67,11 @@ public final class BindingAdapters {
|
||||
listener.setList(newList);
|
||||
}
|
||||
|
||||
@BindingAdapter({"items", "layout"})
|
||||
public static <K, E extends Keyed<? extends K>>
|
||||
void setItems(final ListView view,
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
|
||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
||||
return;
|
||||
// The ListAdapter interface is not generic, so this cannot be checked.
|
||||
@SuppressWarnings("unchecked") ObservableKeyedListAdapter<K, E> adapter =
|
||||
(ObservableKeyedListAdapter<K, E>) view.getAdapter();
|
||||
// If the layout changes, any existing adapter must be replaced.
|
||||
if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
|
||||
adapter.setList(null);
|
||||
adapter = null;
|
||||
}
|
||||
// Avoid setting an adapter when there is no new list or layout.
|
||||
if (newList == null || newLayoutId == 0)
|
||||
return;
|
||||
if (adapter == null) {
|
||||
adapter = new ObservableKeyedListAdapter<>(view.getContext(), newLayoutId, newList);
|
||||
view.setAdapter(adapter);
|
||||
}
|
||||
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||
adapter.setList(newList);
|
||||
}
|
||||
|
||||
@BindingAdapter({"items", "layout"})
|
||||
@BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
|
||||
public static <K, E extends Keyed<? extends K>>
|
||||
void setItems(final RecyclerView view,
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId) {
|
||||
final ObservableKeyedList<K, E> oldList, final int oldLayoutId, final RowConfigurationHandler oldRowConfigurationHandler,
|
||||
final ObservableKeyedList<K, E> newList, final int newLayoutId, final RowConfigurationHandler newRowConfigurationHandler) {
|
||||
if (view.getLayoutManager() == null)
|
||||
view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
|
||||
|
||||
@ -118,6 +92,8 @@ public final class BindingAdapters {
|
||||
adapter = new ObservableKeyedRecyclerViewAdapter<>(view.getContext(), newLayoutId, newList);
|
||||
view.setAdapter(adapter);
|
||||
}
|
||||
|
||||
adapter.setRowConfigurationHandler(newRowConfigurationHandler);
|
||||
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||
adapter.setList(newList);
|
||||
}
|
||||
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2018 Samuel Holland <samuel@sholland.org>
|
||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package com.wireguard.android.databinding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.databinding.DataBindingUtil;
|
||||
import android.databinding.ObservableList;
|
||||
import android.databinding.ViewDataBinding;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
import com.wireguard.util.Keyed;
|
||||
import com.wireguard.android.util.ObservableKeyedList;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* A generic {@code ListAdapter} backed by a {@code ObservableKeyedList}.
|
||||
*/
|
||||
|
||||
class ObservableKeyedListAdapter<K, E extends Keyed<? extends K>> extends BaseAdapter {
|
||||
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
|
||||
private final int layoutId;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private ObservableKeyedList<K, E> list;
|
||||
|
||||
ObservableKeyedListAdapter(final Context context, final int layoutId,
|
||||
final ObservableKeyedList<K, E> list) {
|
||||
this.layoutId = layoutId;
|
||||
layoutInflater = LayoutInflater.from(context);
|
||||
setList(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return list != null ? list.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getItem(final int position) {
|
||||
if (list == null || position < 0 || position >= list.size())
|
||||
return null;
|
||||
return list.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int position) {
|
||||
final K key = getKey(position);
|
||||
return key != null ? key.hashCode() : -1;
|
||||
}
|
||||
|
||||
private K getKey(final int position) {
|
||||
final E item = getItem(position);
|
||||
return item != null ? item.getKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, final View convertView, final ViewGroup parent) {
|
||||
ViewDataBinding binding = DataBindingUtil.getBinding(convertView);
|
||||
if (binding == null)
|
||||
binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false);
|
||||
binding.setVariable(BR.collection, list);
|
||||
binding.setVariable(BR.key, getKey(position));
|
||||
binding.setVariable(BR.item, getItem(position));
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setList(final ObservableKeyedList<K, E> newList) {
|
||||
if (list != null)
|
||||
list.removeOnListChangedCallback(callback);
|
||||
list = newList;
|
||||
if (list != null) {
|
||||
list.addOnListChangedCallback(callback);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private static final class OnListChangedCallback<E extends Keyed<?>>
|
||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
||||
|
||||
private final WeakReference<ObservableKeyedListAdapter<?, E>> weakAdapter;
|
||||
|
||||
private OnListChangedCallback(final ObservableKeyedListAdapter<?, E> adapter) {
|
||||
weakAdapter = new WeakReference<>(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(final ObservableList<E> sender) {
|
||||
final ObservableKeyedListAdapter adapter = weakAdapter.get();
|
||||
if (adapter != null)
|
||||
adapter.notifyDataSetChanged();
|
||||
else
|
||||
sender.removeOnListChangedCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(final ObservableList<E> sender, final int fromPosition,
|
||||
final int toPosition, final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(final ObservableList<E> sender, final int positionStart,
|
||||
final int itemCount) {
|
||||
onChanged(sender);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.wireguard.android.BR;
|
||||
@ -26,12 +27,13 @@ import java.lang.ref.WeakReference;
|
||||
* A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
|
||||
*/
|
||||
|
||||
class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extends Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder> {
|
||||
public class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extends Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
private final OnListChangedCallback<E> callback = new OnListChangedCallback<>(this);
|
||||
private final int layoutId;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private ObservableKeyedList<K, E> list;
|
||||
private RowConfigurationHandler rowConfigurationHandler;
|
||||
|
||||
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
|
||||
final ObservableKeyedList<K, E> list) {
|
||||
@ -67,12 +69,17 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
|
||||
holder.binding.setVariable(BR.collection, list);
|
||||
holder.binding.setVariable(BR.key, getKey(position));
|
||||
holder.binding.setVariable(BR.item, getItem(position));
|
||||
holder.binding.executePendingBindings();
|
||||
|
||||
if (rowConfigurationHandler != null) {
|
||||
rowConfigurationHandler.onConfigureRow(holder.binding.getRoot(), getItem(position), position);
|
||||
}
|
||||
}
|
||||
|
||||
void setList(final ObservableKeyedList<K, E> newList) {
|
||||
@ -85,6 +92,10 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
|
||||
this.rowConfigurationHandler = rowConfigurationHandler;
|
||||
}
|
||||
|
||||
private static final class OnListChangedCallback<E extends Keyed<?>>
|
||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
||||
|
||||
@ -138,4 +149,8 @@ class ObservableKeyedRecyclerViewAdapter<K, E extends Keyed<? extends K>> extend
|
||||
}
|
||||
}
|
||||
|
||||
public interface RowConfigurationHandler<T> {
|
||||
void onConfigureRow(View view, T item, int position);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,26 +16,21 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemLongClickListener;
|
||||
|
||||
import com.wireguard.android.Application;
|
||||
import com.wireguard.android.R;
|
||||
import com.wireguard.android.activity.TunnelCreatorActivity;
|
||||
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter;
|
||||
import com.wireguard.android.databinding.TunnelListFragmentBinding;
|
||||
import com.wireguard.android.model.Tunnel;
|
||||
import com.wireguard.android.util.ExceptionLoggers;
|
||||
@ -47,13 +42,13 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java9.util.stream.Collectors;
|
||||
import java9.util.stream.IntStream;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
@ -64,8 +59,7 @@ public class TunnelListFragment extends BaseFragment {
|
||||
private static final int REQUEST_IMPORT = 1;
|
||||
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
|
||||
|
||||
private final MultiChoiceModeListener actionModeListener = new ActionModeListener();
|
||||
private final ListViewCallbacks listViewCallbacks = new ListViewCallbacks();
|
||||
private final ActionModeListener actionModeListener = new ActionModeListener();
|
||||
private ActionMode actionMode;
|
||||
private TunnelListFragmentBinding binding;
|
||||
|
||||
@ -182,20 +176,19 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
|
||||
binding.tunnelList.setMultiChoiceModeListener(actionModeListener);
|
||||
binding.tunnelList.setOnItemClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnItemLongClickListener(listViewCallbacks);
|
||||
binding.tunnelList.setOnTouchListener(listViewCallbacks);
|
||||
|
||||
binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
|
||||
if (binding != null) {
|
||||
binding.createMenu.collapse();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
binding.executePendingBindings();
|
||||
return binding.getRoot();
|
||||
}
|
||||
@ -276,38 +269,54 @@ public class TunnelListFragment extends BaseFragment {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
binding.setFragment(this);
|
||||
binding.setTunnels(Application.getTunnelManager().getTunnels());
|
||||
binding.setRowConfigurationHandler(new ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler<Tunnel>() {
|
||||
@Override
|
||||
public void onConfigureRow(View view, Tunnel tunnel, int position) {
|
||||
view.setOnClickListener(clicked -> {
|
||||
if (actionMode == null) {
|
||||
setSelectedTunnel(tunnel);
|
||||
} else {
|
||||
actionModeListener.toggleItemChecked(position);
|
||||
}
|
||||
});
|
||||
view.setOnLongClickListener(clicked -> {
|
||||
actionModeListener.toggleItemChecked(position);
|
||||
return true;
|
||||
});
|
||||
|
||||
view.setActivated(actionModeListener.checkedItems.contains(position));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final class ActionModeListener implements MultiChoiceModeListener {
|
||||
private Resources resources;
|
||||
private AbsListView tunnelList;
|
||||
private final class ActionModeListener implements ActionMode.Callback {
|
||||
private final Set<Integer> checkedItems = new HashSet<>();
|
||||
|
||||
private IntStream getCheckedPositions() {
|
||||
final SparseBooleanArray checkedItemPositions = tunnelList.getCheckedItemPositions();
|
||||
return IntStream.range(0, checkedItemPositions.size())
|
||||
.filter(checkedItemPositions::valueAt)
|
||||
.map(checkedItemPositions::keyAt);
|
||||
}
|
||||
private Resources resources;
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_action_delete:
|
||||
// Must operate in two steps: positions change once we start deleting things.
|
||||
final List<Tunnel> tunnelsToDelete = getCheckedPositions()
|
||||
.mapToObj(pos -> (Tunnel) tunnelList.getItemAtPosition(pos))
|
||||
.collect(Collectors.toList());
|
||||
List<Tunnel> tunnelsToDelete = new ArrayList<>();
|
||||
for (Integer position : checkedItems) {
|
||||
tunnelsToDelete.add(Application.getTunnelManager().getTunnels().get(position));
|
||||
}
|
||||
|
||||
final CompletableFuture[] futures = StreamSupport.stream(tunnelsToDelete)
|
||||
.map(Tunnel::delete)
|
||||
.toArray(CompletableFuture[]::new);
|
||||
CompletableFuture.allOf(futures)
|
||||
.thenApply(x -> futures.length)
|
||||
.whenComplete(TunnelListFragment.this::onTunnelDeletionFinished);
|
||||
|
||||
checkedItems.clear();
|
||||
mode.finish();
|
||||
return true;
|
||||
case R.id.menu_action_select_all:
|
||||
for (int i = 0; i < tunnelList.getAdapter().getCount(); ++i)
|
||||
tunnelList.setItemChecked(i, true);
|
||||
for (int i = 0; i < Application.getTunnelManager().getTunnels().size(); ++i) {
|
||||
setItemChecked(i, true);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@ -317,9 +326,9 @@ public class TunnelListFragment extends BaseFragment {
|
||||
@Override
|
||||
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
|
||||
actionMode = mode;
|
||||
if (getActivity() != null)
|
||||
if (getActivity() != null) {
|
||||
resources = getActivity().getResources();
|
||||
tunnelList = binding.tunnelList;
|
||||
}
|
||||
mode.getMenuInflater().inflate(R.menu.tunnel_list_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
@ -328,12 +337,31 @@ public class TunnelListFragment extends BaseFragment {
|
||||
public void onDestroyActionMode(final ActionMode mode) {
|
||||
actionMode = null;
|
||||
resources = null;
|
||||
|
||||
checkedItems.clear();
|
||||
binding.tunnelList.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(final ActionMode mode, final int position,
|
||||
final long id, final boolean checked) {
|
||||
updateTitle(mode);
|
||||
void toggleItemChecked(int position) {
|
||||
setItemChecked(position, !checkedItems.contains(position));
|
||||
}
|
||||
|
||||
void setItemChecked(int position, boolean checked) {
|
||||
if (checked) {
|
||||
checkedItems.add(position);
|
||||
} else {
|
||||
checkedItems.remove(position);
|
||||
}
|
||||
|
||||
if (actionMode == null && !checkedItems.isEmpty() && getActivity() != null) {
|
||||
((AppCompatActivity) getActivity()).startSupportActionMode(this);
|
||||
} else if (actionMode != null && checkedItems.isEmpty()) {
|
||||
actionMode.finish();
|
||||
}
|
||||
|
||||
binding.tunnelList.getAdapter().notifyItemChanged(position);
|
||||
|
||||
updateTitle(actionMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -342,8 +370,12 @@ public class TunnelListFragment extends BaseFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateTitle(final ActionMode mode) {
|
||||
final int count = (int) getCheckedPositions().count();
|
||||
private void updateTitle(@Nullable final ActionMode mode) {
|
||||
if (mode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int count = checkedItems.size();
|
||||
if (count == 0) {
|
||||
mode.setTitle("");
|
||||
} else {
|
||||
@ -352,30 +384,4 @@ public class TunnelListFragment extends BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ListViewCallbacks
|
||||
implements OnItemClickListener, OnItemLongClickListener, OnTouchListener {
|
||||
@Override
|
||||
public void onItemClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
setSelectedTunnel((Tunnel) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
|
||||
final int position, final long id) {
|
||||
if (actionMode != null)
|
||||
return false;
|
||||
if (binding != null)
|
||||
binding.tunnelList.setItemChecked(position, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(final View view, final MotionEvent motionEvent) {
|
||||
if (binding != null)
|
||||
binding.createMenu.collapse();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,5 @@
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/list_item_ripple"> <!-- TODO(msf): themeify this -->
|
||||
<item android:drawable="@drawable/list_item_background" />
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/white" />
|
||||
</ripple>
|
||||
|
@ -10,6 +10,10 @@
|
||||
name="fragment"
|
||||
type="com.wireguard.android.fragment.TunnelListFragment" />
|
||||
|
||||
<variable
|
||||
name="rowConfigurationHandler"
|
||||
type="com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler" />
|
||||
|
||||
<variable
|
||||
name="tunnels"
|
||||
type="com.wireguard.android.util.ObservableKeyedList<String, Tunnel>" />
|
||||
@ -21,13 +25,14 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<ListView
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/tunnel_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="multipleChoiceModal"
|
||||
app:items="@{tunnels}"
|
||||
app:layout="@{@layout/tunnel_list_item}" />
|
||||
app:layout="@{@layout/tunnel_list_item}"
|
||||
app:configurationHandler="@{rowConfigurationHandler}" />
|
||||
|
||||
<com.wireguard.android.widget.fab.FloatingActionsMenu
|
||||
android:id="@+id/create_menu"
|
||||
|
Loading…
Reference in New Issue
Block a user