databinding: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
8669c01eaa
commit
48a9fd46a6
@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.databinding;
|
|
||||||
|
|
||||||
import android.text.InputFilter;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.wireguard.android.BR;
|
|
||||||
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;
|
|
||||||
import com.wireguard.config.Attribute;
|
|
||||||
import com.wireguard.config.InetNetwork;
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.databinding.BindingAdapter;
|
|
||||||
import androidx.databinding.DataBindingUtil;
|
|
||||||
import androidx.databinding.ObservableList;
|
|
||||||
import androidx.databinding.ViewDataBinding;
|
|
||||||
import androidx.databinding.adapters.ListenerUtil;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import java9.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static methods for use by generated code in the Android data binding library.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@NonNullForAll
|
|
||||||
public final class BindingAdapters {
|
|
||||||
private BindingAdapters() {
|
|
||||||
// Prevent instantiation.
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("checked")
|
|
||||||
public static void setChecked(final ToggleSwitch view, final boolean checked) {
|
|
||||||
view.setCheckedInternal(checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("filter")
|
|
||||||
public static void setFilter(final TextView view, final InputFilter filter) {
|
|
||||||
view.setFilters(new InputFilter[]{filter});
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter({"items", "layout"})
|
|
||||||
public static <E>
|
|
||||||
void setItems(final LinearLayout view,
|
|
||||||
@Nullable final ObservableList<E> oldList, final int oldLayoutId,
|
|
||||||
@Nullable final ObservableList<E> newList, final int newLayoutId) {
|
|
||||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
|
||||||
return;
|
|
||||||
ItemChangeListener<E> listener = ListenerUtil.getListener(view, R.id.item_change_listener);
|
|
||||||
// If the layout changes, any existing listener must be replaced.
|
|
||||||
if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
|
|
||||||
listener.setList(null);
|
|
||||||
listener = null;
|
|
||||||
// Stop tracking the old listener.
|
|
||||||
ListenerUtil.trackListener(view, null, R.id.item_change_listener);
|
|
||||||
}
|
|
||||||
// Avoid adding a listener when there is no new list or layout.
|
|
||||||
if (newList == null || newLayoutId == 0)
|
|
||||||
return;
|
|
||||||
if (listener == null) {
|
|
||||||
listener = new ItemChangeListener<>(view, newLayoutId);
|
|
||||||
ListenerUtil.trackListener(view, listener, R.id.item_change_listener);
|
|
||||||
}
|
|
||||||
// Either the list changed, or this is an entirely new listener because the layout changed.
|
|
||||||
listener.setList(newList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter({"items", "layout"})
|
|
||||||
public static <E>
|
|
||||||
void setItems(final LinearLayout view,
|
|
||||||
@Nullable final Iterable<E> oldList, final int oldLayoutId,
|
|
||||||
@Nullable final Iterable<E> newList, final int newLayoutId) {
|
|
||||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
|
||||||
return;
|
|
||||||
view.removeAllViews();
|
|
||||||
if (newList == null)
|
|
||||||
return;
|
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(view.getContext());
|
|
||||||
for (final E item : newList) {
|
|
||||||
final ViewDataBinding binding =
|
|
||||||
DataBindingUtil.inflate(layoutInflater, newLayoutId, view, false);
|
|
||||||
binding.setVariable(BR.collection, newList);
|
|
||||||
binding.setVariable(BR.item, item);
|
|
||||||
binding.executePendingBindings();
|
|
||||||
view.addView(binding.getRoot());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter(requireAll = false, value = {"items", "layout", "configurationHandler"})
|
|
||||||
public static <K, E extends Keyed<? extends K>>
|
|
||||||
void setItems(final RecyclerView view,
|
|
||||||
@Nullable final ObservableKeyedList<K, E> oldList, final int oldLayoutId,
|
|
||||||
final RowConfigurationHandler oldRowConfigurationHandler,
|
|
||||||
@Nullable final ObservableKeyedList<K, E> newList, final int newLayoutId,
|
|
||||||
final RowConfigurationHandler newRowConfigurationHandler) {
|
|
||||||
if (view.getLayoutManager() == null)
|
|
||||||
view.setLayoutManager(new LinearLayoutManager(view.getContext(), RecyclerView.VERTICAL, false));
|
|
||||||
|
|
||||||
if (oldList == newList && oldLayoutId == newLayoutId)
|
|
||||||
return;
|
|
||||||
// The ListAdapter interface is not generic, so this cannot be checked.
|
|
||||||
@SuppressWarnings("unchecked") ObservableKeyedRecyclerViewAdapter<K, E> adapter =
|
|
||||||
(ObservableKeyedRecyclerViewAdapter<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 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("onBeforeCheckedChanged")
|
|
||||||
public static void setOnBeforeCheckedChanged(final ToggleSwitch view,
|
|
||||||
final OnBeforeCheckedChangeListener listener) {
|
|
||||||
view.setOnBeforeCheckedChangeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("android:text")
|
|
||||||
public static void setText(final TextView view, final Optional<?> text) {
|
|
||||||
view.setText(text.map(Object::toString).orElse(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
@BindingAdapter("android:text")
|
|
||||||
public static void setText(final TextView view, @Nullable final Iterable<InetNetwork> networks) {
|
|
||||||
view.setText(networks != null ? Attribute.join(networks) : "");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
import android.text.InputFilter
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.databinding.adapters.ListenerUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.wireguard.android.BR
|
||||||
|
import com.wireguard.android.R
|
||||||
|
import com.wireguard.android.databinding.ObservableKeyedRecyclerViewAdapter.RowConfigurationHandler
|
||||||
|
import com.wireguard.android.widget.ToggleSwitch
|
||||||
|
import com.wireguard.android.widget.ToggleSwitch.OnBeforeCheckedChangeListener
|
||||||
|
import com.wireguard.config.Attribute
|
||||||
|
import com.wireguard.config.InetNetwork
|
||||||
|
import java9.util.Optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static methods for use by generated code in the Android data binding library.
|
||||||
|
*/
|
||||||
|
object BindingAdapters {
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("checked")
|
||||||
|
fun setChecked(view: ToggleSwitch, checked: Boolean) {
|
||||||
|
view.setCheckedInternal(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("filter")
|
||||||
|
fun setFilter(view: TextView, filter: InputFilter) {
|
||||||
|
view.filters = arrayOf(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("items", "layout")
|
||||||
|
fun <E> setItems(view: LinearLayout,
|
||||||
|
oldList: ObservableList<E>?, oldLayoutId: Int,
|
||||||
|
newList: ObservableList<E>?, newLayoutId: Int) {
|
||||||
|
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||||
|
return
|
||||||
|
var listener: ItemChangeListener<E>? = ListenerUtil.getListener(view, R.id.item_change_listener)
|
||||||
|
// If the layout changes, any existing listener must be replaced.
|
||||||
|
if (listener != null && oldList != null && oldLayoutId != newLayoutId) {
|
||||||
|
listener.setList(null)
|
||||||
|
listener = null
|
||||||
|
// Stop tracking the old listener.
|
||||||
|
ListenerUtil.trackListener<Any?>(view, null, R.id.item_change_listener)
|
||||||
|
}
|
||||||
|
// Avoid adding a listener when there is no new list or layout.
|
||||||
|
if (newList == null || newLayoutId == 0)
|
||||||
|
return
|
||||||
|
if (listener == null) {
|
||||||
|
listener = ItemChangeListener(view, newLayoutId)
|
||||||
|
ListenerUtil.trackListener(view, listener, R.id.item_change_listener)
|
||||||
|
}
|
||||||
|
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||||
|
listener.setList(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("items", "layout")
|
||||||
|
fun <E> setItems(view: LinearLayout,
|
||||||
|
oldList: Iterable<E>?, oldLayoutId: Int,
|
||||||
|
newList: Iterable<E>?, newLayoutId: Int) {
|
||||||
|
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||||
|
return
|
||||||
|
view.removeAllViews()
|
||||||
|
if (newList == null)
|
||||||
|
return
|
||||||
|
val layoutInflater = LayoutInflater.from(view.context)
|
||||||
|
for (item in newList) {
|
||||||
|
val binding = DataBindingUtil.inflate<ViewDataBinding>(layoutInflater, newLayoutId, view, false)
|
||||||
|
binding.setVariable(BR.collection, newList)
|
||||||
|
binding.setVariable(BR.item, item)
|
||||||
|
binding.executePendingBindings()
|
||||||
|
view.addView(binding.root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter(requireAll = false, value = ["items", "layout", "configurationHandler"])
|
||||||
|
fun <K, E : Keyed<out K>> setItems(view: RecyclerView,
|
||||||
|
oldList: ObservableKeyedArrayList<K, E>?, oldLayoutId: Int,
|
||||||
|
oldRowConfigurationHandler: RowConfigurationHandler<*, *>?,
|
||||||
|
newList: ObservableKeyedArrayList<K, E>?, newLayoutId: Int,
|
||||||
|
newRowConfigurationHandler: RowConfigurationHandler<*, *>?) {
|
||||||
|
if (view.layoutManager == null)
|
||||||
|
view.layoutManager = LinearLayoutManager(view.context, RecyclerView.VERTICAL, false)
|
||||||
|
if (oldList === newList && oldLayoutId == newLayoutId)
|
||||||
|
return
|
||||||
|
// The ListAdapter interface is not generic, so this cannot be checked.
|
||||||
|
var adapter = view.adapter as ObservableKeyedRecyclerViewAdapter<K, E>?
|
||||||
|
// 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 = ObservableKeyedRecyclerViewAdapter(view.context, newLayoutId, newList)
|
||||||
|
view.adapter = adapter
|
||||||
|
}
|
||||||
|
adapter.setRowConfigurationHandler(newRowConfigurationHandler)
|
||||||
|
// Either the list changed, or this is an entirely new listener because the layout changed.
|
||||||
|
adapter.setList(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("onBeforeCheckedChanged")
|
||||||
|
fun setOnBeforeCheckedChanged(view: ToggleSwitch,
|
||||||
|
listener: OnBeforeCheckedChangeListener?) {
|
||||||
|
view.setOnBeforeCheckedChangeListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("android:text")
|
||||||
|
fun setText(view: TextView, text: Optional<*>) {
|
||||||
|
view.text = text.map { obj: Any -> obj.toString() }.orElse("")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BindingAdapter("android:text")
|
||||||
|
fun setText(view: TextView, networks: Iterable<InetNetwork?>?) {
|
||||||
|
view.text = if (networks != null) Attribute.join(networks) else ""
|
||||||
|
}
|
||||||
|
}
|
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.databinding;
|
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.wireguard.android.BR;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.databinding.DataBindingUtil;
|
|
||||||
import androidx.databinding.ObservableList;
|
|
||||||
import androidx.databinding.ViewDataBinding;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for binding an ObservableList to the children of a ViewGroup.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
class ItemChangeListener<T> {
|
|
||||||
private final OnListChangedCallback<T> callback = new OnListChangedCallback<>(this);
|
|
||||||
private final ViewGroup container;
|
|
||||||
private final int layoutId;
|
|
||||||
private final LayoutInflater layoutInflater;
|
|
||||||
@Nullable private ObservableList<T> list;
|
|
||||||
|
|
||||||
ItemChangeListener(final ViewGroup container, final int layoutId) {
|
|
||||||
this.container = container;
|
|
||||||
this.layoutId = layoutId;
|
|
||||||
layoutInflater = LayoutInflater.from(container.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private View getView(final int position, @Nullable final View convertView) {
|
|
||||||
ViewDataBinding binding = convertView != null ? DataBindingUtil.getBinding(convertView) : null;
|
|
||||||
if (binding == null) {
|
|
||||||
binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Objects.requireNonNull(list, "Trying to get a view while list is still null");
|
|
||||||
|
|
||||||
binding.setVariable(BR.collection, list);
|
|
||||||
binding.setVariable(BR.item, list.get(position));
|
|
||||||
binding.executePendingBindings();
|
|
||||||
return binding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setList(@Nullable final ObservableList<T> newList) {
|
|
||||||
if (list != null)
|
|
||||||
list.removeOnListChangedCallback(callback);
|
|
||||||
list = newList;
|
|
||||||
if (list != null) {
|
|
||||||
list.addOnListChangedCallback(callback);
|
|
||||||
callback.onChanged(list);
|
|
||||||
} else {
|
|
||||||
container.removeAllViews();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class OnListChangedCallback<T>
|
|
||||||
extends ObservableList.OnListChangedCallback<ObservableList<T>> {
|
|
||||||
|
|
||||||
private final WeakReference<ItemChangeListener<T>> weakListener;
|
|
||||||
|
|
||||||
private OnListChangedCallback(final ItemChangeListener<T> listener) {
|
|
||||||
weakListener = new WeakReference<>(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(final ObservableList<T> sender) {
|
|
||||||
final ItemChangeListener<T> listener = weakListener.get();
|
|
||||||
if (listener != null) {
|
|
||||||
// TODO: recycle views
|
|
||||||
listener.container.removeAllViews();
|
|
||||||
for (int i = 0; i < sender.size(); ++i)
|
|
||||||
listener.container.addView(listener.getView(i, null));
|
|
||||||
} else {
|
|
||||||
sender.removeOnListChangedCallback(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeChanged(final ObservableList<T> sender, final int positionStart,
|
|
||||||
final int itemCount) {
|
|
||||||
final ItemChangeListener<T> listener = weakListener.get();
|
|
||||||
if (listener != null) {
|
|
||||||
for (int i = positionStart; i < positionStart + itemCount; ++i) {
|
|
||||||
final View child = listener.container.getChildAt(i);
|
|
||||||
listener.container.removeViewAt(i);
|
|
||||||
listener.container.addView(listener.getView(i, child));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sender.removeOnListChangedCallback(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeInserted(final ObservableList<T> sender, final int positionStart,
|
|
||||||
final int itemCount) {
|
|
||||||
final ItemChangeListener<T> listener = weakListener.get();
|
|
||||||
if (listener != null) {
|
|
||||||
for (int i = positionStart; i < positionStart + itemCount; ++i)
|
|
||||||
listener.container.addView(listener.getView(i, null));
|
|
||||||
} else {
|
|
||||||
sender.removeOnListChangedCallback(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeMoved(final ObservableList<T> sender, final int fromPosition,
|
|
||||||
final int toPosition, final int itemCount) {
|
|
||||||
final ItemChangeListener<T> listener = weakListener.get();
|
|
||||||
if (listener != null) {
|
|
||||||
final View[] views = new View[itemCount];
|
|
||||||
for (int i = 0; i < itemCount; ++i)
|
|
||||||
views[i] = listener.container.getChildAt(fromPosition + i);
|
|
||||||
listener.container.removeViews(fromPosition, itemCount);
|
|
||||||
for (int i = 0; i < itemCount; ++i)
|
|
||||||
listener.container.addView(views[i], toPosition + i);
|
|
||||||
} else {
|
|
||||||
sender.removeOnListChangedCallback(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeRemoved(final ObservableList<T> sender, final int positionStart,
|
|
||||||
final int itemCount) {
|
|
||||||
final ItemChangeListener<T> listener = weakListener.get();
|
|
||||||
if (listener != null) {
|
|
||||||
listener.container.removeViews(positionStart, itemCount);
|
|
||||||
} else {
|
|
||||||
sender.removeOnListChangedCallback(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import com.wireguard.android.BR
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for binding an ObservableList to the children of a ViewGroup.
|
||||||
|
*/
|
||||||
|
internal class ItemChangeListener<T>(private val container: ViewGroup, private val layoutId: Int) {
|
||||||
|
private val callback = OnListChangedCallback(this)
|
||||||
|
private val layoutInflater: LayoutInflater = LayoutInflater.from(container.context)
|
||||||
|
private var list: ObservableList<T>? = null
|
||||||
|
|
||||||
|
private fun getView(position: Int, convertView: View?): View {
|
||||||
|
var binding = if (convertView != null) DataBindingUtil.getBinding<ViewDataBinding>(convertView) else null
|
||||||
|
if (binding == null) {
|
||||||
|
binding = DataBindingUtil.inflate(layoutInflater, layoutId, container, false)
|
||||||
|
}
|
||||||
|
require(list != null) { "Trying to get a view while list is still null" }
|
||||||
|
binding!!.setVariable(BR.collection, list)
|
||||||
|
binding.setVariable(BR.item, list!![position])
|
||||||
|
binding.executePendingBindings()
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setList(newList: ObservableList<T>?) {
|
||||||
|
list?.removeOnListChangedCallback(callback)
|
||||||
|
list = newList
|
||||||
|
if (list != null) {
|
||||||
|
list!!.addOnListChangedCallback(callback)
|
||||||
|
callback.onChanged(list!!)
|
||||||
|
} else {
|
||||||
|
container.removeAllViews()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnListChangedCallback<T> constructor(listener: ItemChangeListener<T>) : ObservableList.OnListChangedCallback<ObservableList<T>>() {
|
||||||
|
private val weakListener: WeakReference<ItemChangeListener<T>> = WeakReference(listener)
|
||||||
|
|
||||||
|
override fun onChanged(sender: ObservableList<T>) {
|
||||||
|
val listener = weakListener.get()
|
||||||
|
if (listener != null) {
|
||||||
|
// TODO: recycle views
|
||||||
|
listener.container.removeAllViews()
|
||||||
|
for (i in sender.indices)
|
||||||
|
listener.container.addView(listener.getView(i, null))
|
||||||
|
} else {
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(sender: ObservableList<T>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
val listener = weakListener.get()
|
||||||
|
if (listener != null) {
|
||||||
|
for (i in positionStart until positionStart + itemCount) {
|
||||||
|
val child = listener.container.getChildAt(i)
|
||||||
|
listener.container.removeViewAt(i)
|
||||||
|
listener.container.addView(listener.getView(i, child))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(sender: ObservableList<T>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
val listener = weakListener.get()
|
||||||
|
if (listener != null) {
|
||||||
|
for (i in positionStart until positionStart + itemCount)
|
||||||
|
listener.container.addView(listener.getView(i, null))
|
||||||
|
} else {
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(sender: ObservableList<T>, fromPosition: Int,
|
||||||
|
toPosition: Int, itemCount: Int) {
|
||||||
|
val listener = weakListener.get()
|
||||||
|
if (listener != null) {
|
||||||
|
val views = arrayOfNulls<View>(itemCount)
|
||||||
|
for (i in 0 until itemCount) views[i] = listener.container.getChildAt(fromPosition + i)
|
||||||
|
listener.container.removeViews(fromPosition, itemCount)
|
||||||
|
for (i in 0 until itemCount) listener.container.addView(views[i], toPosition + i)
|
||||||
|
} else {
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(sender: ObservableList<T>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
val listener = weakListener.get()
|
||||||
|
if (listener != null) {
|
||||||
|
listener.container.removeViews(positionStart, itemCount)
|
||||||
|
} else {
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
Normal file
12
ui/src/main/java/com/wireguard/android/databinding/Keyed.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for objects that have a identifying key of the given type.
|
||||||
|
*/
|
||||||
|
interface Keyed<K> {
|
||||||
|
val key: K
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayList that allows looking up elements by some key property. As the key property must always
|
||||||
|
* be retrievable, this list cannot hold `null` elements. Because this class places no
|
||||||
|
* restrictions on the order or duplication of keys, lookup by key, as well as all list modification
|
||||||
|
* operations, require O(n) time.
|
||||||
|
*/
|
||||||
|
open class ObservableKeyedArrayList<K, E : Keyed<out K>> : ObservableArrayList<E>() {
|
||||||
|
fun containsKey(key: K) = indexOfKey(key) >= 0
|
||||||
|
|
||||||
|
operator fun get(key: K): E? {
|
||||||
|
val index = indexOfKey(key)
|
||||||
|
return if (index >= 0) get(index) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun indexOfKey(key: K): Int {
|
||||||
|
val iterator = listIterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val index = iterator.nextIndex()
|
||||||
|
if (iterator.next()!!.key == key)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
@ -1,162 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.databinding;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.wireguard.android.BR;
|
|
||||||
import com.wireguard.android.util.ObservableKeyedList;
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.databinding.DataBindingUtil;
|
|
||||||
import androidx.databinding.ObservableList;
|
|
||||||
import androidx.databinding.ViewDataBinding;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic {@code RecyclerView.Adapter} backed by a {@code ObservableKeyedList}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
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;
|
|
||||||
@Nullable private ObservableKeyedList<K, E> list;
|
|
||||||
@Nullable private RowConfigurationHandler rowConfigurationHandler;
|
|
||||||
|
|
||||||
ObservableKeyedRecyclerViewAdapter(final Context context, final int layoutId,
|
|
||||||
final ObservableKeyedList<K, E> list) {
|
|
||||||
this.layoutId = layoutId;
|
|
||||||
layoutInflater = LayoutInflater.from(context);
|
|
||||||
setList(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private E getItem(final int position) {
|
|
||||||
if (list == null || position < 0 || position >= list.size())
|
|
||||||
return null;
|
|
||||||
return list.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return list != null ? list.size() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(final int position) {
|
|
||||||
final K key = getKey(position);
|
|
||||||
return key != null ? key.hashCode() : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private K getKey(final int position) {
|
|
||||||
final E item = getItem(position);
|
|
||||||
return item != null ? item.getKey() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(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) {
|
|
||||||
final E item = getItem(position);
|
|
||||||
if (item != null) {
|
|
||||||
rowConfigurationHandler.onConfigureRow(holder.binding, item, position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
|
|
||||||
return new ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setList(@Nullable final ObservableKeyedList<K, E> newList) {
|
|
||||||
if (list != null)
|
|
||||||
list.removeOnListChangedCallback(callback);
|
|
||||||
list = newList;
|
|
||||||
if (list != null) {
|
|
||||||
list.addOnListChangedCallback(callback);
|
|
||||||
}
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRowConfigurationHandler(final RowConfigurationHandler rowConfigurationHandler) {
|
|
||||||
this.rowConfigurationHandler = rowConfigurationHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface RowConfigurationHandler<B extends ViewDataBinding, T> {
|
|
||||||
void onConfigureRow(B binding, T item, int position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class OnListChangedCallback<E extends Keyed<?>>
|
|
||||||
extends ObservableList.OnListChangedCallback<ObservableList<E>> {
|
|
||||||
|
|
||||||
private final WeakReference<ObservableKeyedRecyclerViewAdapter<?, E>> weakAdapter;
|
|
||||||
|
|
||||||
private OnListChangedCallback(final ObservableKeyedRecyclerViewAdapter<?, E> adapter) {
|
|
||||||
weakAdapter = new WeakReference<>(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(final ObservableList<E> sender) {
|
|
||||||
final ObservableKeyedRecyclerViewAdapter 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
final ViewDataBinding binding;
|
|
||||||
|
|
||||||
public ViewHolder(final ViewDataBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.wireguard.android.BR
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic `RecyclerView.Adapter` backed by a `ObservableKeyedArrayList`.
|
||||||
|
*/
|
||||||
|
class ObservableKeyedRecyclerViewAdapter<K, E : Keyed<out K>> internal constructor(context: Context, private val layoutId: Int,
|
||||||
|
list: ObservableKeyedArrayList<K, E>?) : RecyclerView.Adapter<ObservableKeyedRecyclerViewAdapter.ViewHolder>() {
|
||||||
|
private val callback = OnListChangedCallback(this)
|
||||||
|
private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
private var list: ObservableKeyedArrayList<K, E>? = null
|
||||||
|
private var rowConfigurationHandler: RowConfigurationHandler<ViewDataBinding, Any>? = null
|
||||||
|
|
||||||
|
private fun getItem(position: Int): E? = if (list == null || position < 0 || position >= list!!.size) null else list?.get(position)
|
||||||
|
|
||||||
|
override fun getItemCount() = list?.size ?: 0
|
||||||
|
|
||||||
|
override fun getItemId(position: Int) = (getKey(position)?.hashCode() ?: -1).toLong()
|
||||||
|
|
||||||
|
private fun getKey(position: Int): K? = getItem(position)?.key
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
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) {
|
||||||
|
val item = getItem(position)
|
||||||
|
if (item != null) {
|
||||||
|
rowConfigurationHandler?.onConfigureRow(holder.binding, item, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(DataBindingUtil.inflate(layoutInflater, layoutId, parent, false))
|
||||||
|
|
||||||
|
fun setList(newList: ObservableKeyedArrayList<K, E>?) {
|
||||||
|
list?.removeOnListChangedCallback(callback)
|
||||||
|
list = newList
|
||||||
|
list?.addOnListChangedCallback(callback)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRowConfigurationHandler(rowConfigurationHandler: RowConfigurationHandler<*, *>?) {
|
||||||
|
this.rowConfigurationHandler = rowConfigurationHandler as? RowConfigurationHandler<ViewDataBinding, Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowConfigurationHandler<B : ViewDataBinding, T> {
|
||||||
|
fun onConfigureRow(binding: B, item: T, position: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnListChangedCallback<E : Keyed<*>> constructor(adapter: ObservableKeyedRecyclerViewAdapter<*, E>) : ObservableList.OnListChangedCallback<ObservableList<E>>() {
|
||||||
|
private val weakAdapter: WeakReference<ObservableKeyedRecyclerViewAdapter<*, E>> = WeakReference(adapter)
|
||||||
|
|
||||||
|
override fun onChanged(sender: ObservableList<E>) {
|
||||||
|
val adapter = weakAdapter.get()
|
||||||
|
if (adapter != null)
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
else
|
||||||
|
sender.removeOnListChangedCallback(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(sender: ObservableList<E>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
onChanged(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(sender: ObservableList<E>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
onChanged(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(sender: ObservableList<E>, fromPosition: Int,
|
||||||
|
toPosition: Int, itemCount: Int) {
|
||||||
|
onChanged(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(sender: ObservableList<E>, positionStart: Int,
|
||||||
|
itemCount: Int) {
|
||||||
|
onChanged(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
init {
|
||||||
|
setList(list)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 WireGuard LLC. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
package com.wireguard.android.databinding
|
||||||
|
|
||||||
|
import java.util.AbstractList
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.Comparator
|
||||||
|
import java.util.NoSuchElementException
|
||||||
|
import java.util.Spliterator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
|
||||||
|
* binary search to improve lookup and replacement times to O(log(n)). However, due to the
|
||||||
|
* array-based nature of this class, insertion and removal of elements with anything but the largest
|
||||||
|
* key still require O(n) time.
|
||||||
|
*/
|
||||||
|
class ObservableSortedKeyedArrayList<K, E : Keyed<out K>>(private val comparator: Comparator<in K>?) : ObservableKeyedArrayList<K, E>() {
|
||||||
|
@Transient
|
||||||
|
private val keyList = KeyList(this)
|
||||||
|
|
||||||
|
override fun add(element: E): Boolean {
|
||||||
|
val insertionPoint = getInsertionPoint(element)
|
||||||
|
if (insertionPoint < 0) {
|
||||||
|
// Skipping insertion is non-destructive if the new and existing objects are the same.
|
||||||
|
if (element === get(-insertionPoint - 1)) return false
|
||||||
|
throw IllegalArgumentException("Element with same key already exists in list")
|
||||||
|
}
|
||||||
|
super.add(insertionPoint, element)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun add(index: Int, element: E) {
|
||||||
|
val insertionPoint = getInsertionPoint(element)
|
||||||
|
require(insertionPoint >= 0) { "Element with same key already exists in list" }
|
||||||
|
if (insertionPoint != index) throw IndexOutOfBoundsException("Wrong index given for element")
|
||||||
|
super.add(index, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<E>): Boolean {
|
||||||
|
var didChange = false
|
||||||
|
for (e in elements) {
|
||||||
|
if (add(e))
|
||||||
|
didChange = true
|
||||||
|
}
|
||||||
|
return didChange
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(index: Int, elements: Collection<E>): Boolean {
|
||||||
|
var i = index
|
||||||
|
for (e in elements)
|
||||||
|
add(i++, e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInsertionPoint(e: E): Int {
|
||||||
|
return if (comparator != null) {
|
||||||
|
-Collections.binarySearch(keyList, e.key, comparator) - 1
|
||||||
|
} else {
|
||||||
|
val list = keyList as List<Comparable<K>>
|
||||||
|
-Collections.binarySearch(list, e.key) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun indexOfKey(key: K): Int {
|
||||||
|
val index: Int
|
||||||
|
index = if (comparator != null) {
|
||||||
|
Collections.binarySearch(keyList, key, comparator)
|
||||||
|
} else {
|
||||||
|
val list = keyList as List<Comparable<K>>
|
||||||
|
Collections.binarySearch(list, key)
|
||||||
|
}
|
||||||
|
return if (index >= 0) index else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(index: Int, element: E): E {
|
||||||
|
val order: Int
|
||||||
|
order = if (comparator != null) {
|
||||||
|
comparator.compare(element.key, get(index).key)
|
||||||
|
} else {
|
||||||
|
val key = element.key as Comparable<K>
|
||||||
|
key.compareTo(get(index).key)
|
||||||
|
}
|
||||||
|
if (order != 0) {
|
||||||
|
// Allow replacement if the new key would be inserted adjacent to the replaced element.
|
||||||
|
val insertionPoint = getInsertionPoint(element)
|
||||||
|
if (insertionPoint < index || insertionPoint > index + 1)
|
||||||
|
throw IndexOutOfBoundsException("Wrong index given for element")
|
||||||
|
}
|
||||||
|
return super.set(index, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun values(): Collection<E> {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyList<K, E : Keyed<out K>>(private val list: ObservableSortedKeyedArrayList<K, E>) : AbstractList<K>(), Set<K> {
|
||||||
|
override fun get(index: Int): K = list[index].key
|
||||||
|
|
||||||
|
override val size
|
||||||
|
get() = list.size
|
||||||
|
|
||||||
|
override fun spliterator(): Spliterator<K> = super<AbstractList>.spliterator()
|
||||||
|
}
|
||||||
|
}
|
@ -17,11 +17,10 @@ import com.wireguard.android.R
|
|||||||
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
import com.wireguard.android.databinding.AppListDialogFragmentBinding
|
||||||
import com.wireguard.android.model.ApplicationData
|
import com.wireguard.android.model.ApplicationData
|
||||||
import com.wireguard.android.util.ErrorMessages
|
import com.wireguard.android.util.ErrorMessages
|
||||||
import com.wireguard.android.util.ObservableKeyedArrayList
|
import com.wireguard.android.databinding.ObservableKeyedArrayList
|
||||||
import com.wireguard.android.util.ObservableKeyedList
|
|
||||||
|
|
||||||
class AppListDialogFragment : DialogFragment() {
|
class AppListDialogFragment : DialogFragment() {
|
||||||
private val appData: ObservableKeyedList<String, ApplicationData> = ObservableKeyedArrayList()
|
private val appData: ObservableKeyedArrayList<String, ApplicationData> = ObservableKeyedArrayList()
|
||||||
private var currentlyExcludedApps = emptyList<String>()
|
private var currentlyExcludedApps = emptyList<String>()
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
|
@ -267,11 +267,13 @@ class TunnelListFragment : BaseFragment() {
|
|||||||
binding ?: return
|
binding ?: return
|
||||||
binding!!.fragment = this
|
binding!!.fragment = this
|
||||||
Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
|
Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
|
||||||
binding!!.rowConfigurationHandler = RowConfigurationHandler { binding: TunnelListItemBinding, tunnel: ObservableTunnel, position ->
|
val parent = this
|
||||||
binding.fragment = this
|
binding!!.rowConfigurationHandler = object : RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel> {
|
||||||
|
override fun onConfigureRow(binding: TunnelListItemBinding, item: ObservableTunnel, position: Int) {
|
||||||
|
binding.fragment = parent
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
if (actionMode == null) {
|
if (actionMode == null) {
|
||||||
selectedTunnel = tunnel
|
selectedTunnel = item
|
||||||
} else {
|
} else {
|
||||||
actionModeListener.toggleItemChecked(position)
|
actionModeListener.toggleItemChecked(position)
|
||||||
}
|
}
|
||||||
@ -283,7 +285,8 @@ class TunnelListFragment : BaseFragment() {
|
|||||||
if (actionMode != null)
|
if (actionMode != null)
|
||||||
(binding.root as MultiselectableRelativeLayout).setMultiSelected(actionModeListener.checkedItems.contains(position))
|
(binding.root as MultiselectableRelativeLayout).setMultiSelected(actionModeListener.checkedItems.contains(position))
|
||||||
else
|
else
|
||||||
(binding.root as MultiselectableRelativeLayout).setSingleSelected(selectedTunnel == tunnel)
|
(binding.root as MultiselectableRelativeLayout).setSingleSelected(selectedTunnel == item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,12 +8,10 @@ import android.graphics.drawable.Drawable
|
|||||||
import androidx.databinding.BaseObservable
|
import androidx.databinding.BaseObservable
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.wireguard.android.BR
|
import com.wireguard.android.BR
|
||||||
import com.wireguard.util.Keyed
|
import com.wireguard.android.databinding.Keyed
|
||||||
|
|
||||||
class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isExcludedFromTunnel: Boolean) : BaseObservable(), Keyed<String> {
|
class ApplicationData(val icon: Drawable, val name: String, val packageName: String, isExcludedFromTunnel: Boolean) : BaseObservable(), Keyed<String> {
|
||||||
override fun getKey(): String {
|
override val key = name
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isExcludedFromTunnel = isExcludedFromTunnel
|
var isExcludedFromTunnel = isExcludedFromTunnel
|
||||||
|
@ -11,7 +11,7 @@ import com.wireguard.android.backend.Statistics
|
|||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.android.util.ExceptionLoggers
|
import com.wireguard.android.util.ExceptionLoggers
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
import com.wireguard.util.Keyed
|
import com.wireguard.android.databinding.Keyed
|
||||||
import java9.util.concurrent.CompletableFuture
|
import java9.util.concurrent.CompletableFuture
|
||||||
import java9.util.concurrent.CompletionStage
|
import java9.util.concurrent.CompletionStage
|
||||||
|
|
||||||
@ -24,8 +24,7 @@ class ObservableTunnel internal constructor(
|
|||||||
config: Config?,
|
config: Config?,
|
||||||
state: Tunnel.State
|
state: Tunnel.State
|
||||||
) : BaseObservable(), Keyed<String>, Tunnel {
|
) : BaseObservable(), Keyed<String>, Tunnel {
|
||||||
override fun getKey() = name
|
override val key = name
|
||||||
|
|
||||||
|
|
||||||
@Bindable
|
@Bindable
|
||||||
override fun getName() = name
|
override fun getName() = name
|
||||||
|
@ -22,8 +22,7 @@ import com.wireguard.android.backend.Statistics
|
|||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.android.configStore.ConfigStore
|
import com.wireguard.android.configStore.ConfigStore
|
||||||
import com.wireguard.android.util.ExceptionLoggers
|
import com.wireguard.android.util.ExceptionLoggers
|
||||||
import com.wireguard.android.util.ObservableSortedKeyedArrayList
|
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
||||||
import com.wireguard.android.util.ObservableSortedKeyedList
|
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
import java9.util.Comparators
|
import java9.util.Comparators
|
||||||
import java9.util.concurrent.CompletableFuture
|
import java9.util.concurrent.CompletableFuture
|
||||||
@ -34,10 +33,10 @@ import java.util.ArrayList
|
|||||||
* Maintains and mediates changes to the set of available WireGuard tunnels,
|
* Maintains and mediates changes to the set of available WireGuard tunnels,
|
||||||
*/
|
*/
|
||||||
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
||||||
val tunnels = CompletableFuture<ObservableSortedKeyedList<String, ObservableTunnel>>()
|
val tunnels = CompletableFuture<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
|
||||||
private val context: Context = get()
|
private val context: Context = get()
|
||||||
private val delayedLoadRestoreTunnels = ArrayList<CompletableFuture<Void>>()
|
private val delayedLoadRestoreTunnels = ArrayList<CompletableFuture<Void>>()
|
||||||
private val tunnelMap: ObservableSortedKeyedList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(COMPARATOR)
|
private val tunnelMap: ObservableSortedKeyedArrayList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(COMPARATOR)
|
||||||
private var haveLoaded = false
|
private var haveLoaded = false
|
||||||
|
|
||||||
private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel? {
|
private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel? {
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.util;
|
|
||||||
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.ListIterator;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.databinding.ObservableArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ArrayList that allows looking up elements by some key property. As the key property must always
|
|
||||||
* be retrievable, this list cannot hold {@code null} elements. Because this class places no
|
|
||||||
* restrictions on the order or duplication of keys, lookup by key, as well as all list modification
|
|
||||||
* operations, require O(n) time.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public class ObservableKeyedArrayList<K, E extends Keyed<? extends K>>
|
|
||||||
extends ObservableArrayList<E> implements ObservableKeyedList<K, E> {
|
|
||||||
@Override
|
|
||||||
public boolean add(@Nullable final E e) {
|
|
||||||
if (e == null)
|
|
||||||
throw new NullPointerException("Trying to add a null element");
|
|
||||||
return super.add(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(final int index, @Nullable final E e) {
|
|
||||||
if (e == null)
|
|
||||||
throw new NullPointerException("Trying to add a null element");
|
|
||||||
super.add(index, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean addAll(final Collection<? extends E> c) {
|
|
||||||
if (c.contains(null))
|
|
||||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
|
||||||
return super.addAll(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean addAll(final int index, final Collection<? extends E> c) {
|
|
||||||
if (c.contains(null))
|
|
||||||
throw new NullPointerException("Trying to add a collection with null element(s)");
|
|
||||||
return super.addAll(index, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsAllKeys(final Collection<K> keys) {
|
|
||||||
for (final K key : keys)
|
|
||||||
if (!containsKey(key))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsKey(final K key) {
|
|
||||||
return indexOfKey(key) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public E get(final K key) {
|
|
||||||
final int index = indexOfKey(key);
|
|
||||||
return index >= 0 ? get(index) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public E getLast(final K key) {
|
|
||||||
final int index = lastIndexOfKey(key);
|
|
||||||
return index >= 0 ? get(index) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int indexOfKey(final K key) {
|
|
||||||
final ListIterator<E> iterator = listIterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
final int index = iterator.nextIndex();
|
|
||||||
if (Objects.equals(iterator.next().getKey(), key))
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int lastIndexOfKey(final K key) {
|
|
||||||
final ListIterator<E> iterator = listIterator(size());
|
|
||||||
while (iterator.hasPrevious()) {
|
|
||||||
final int index = iterator.previousIndex();
|
|
||||||
if (Objects.equals(iterator.previous().getKey(), key))
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E set(final int index, @Nullable final E e) {
|
|
||||||
if (e == null)
|
|
||||||
throw new NullPointerException("Trying to set a null key");
|
|
||||||
return super.set(index, e);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.util;
|
|
||||||
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.KeyedList;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
|
|
||||||
import androidx.databinding.ObservableList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list that is both keyed and observable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public interface ObservableKeyedList<K, E extends Keyed<? extends K>>
|
|
||||||
extends KeyedList<K, E>, ObservableList<E> {
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.util;
|
|
||||||
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
import com.wireguard.util.SortedKeyedList;
|
|
||||||
|
|
||||||
import java.util.AbstractList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Spliterator;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
|
|
||||||
* binary search to improve lookup and replacement times to O(log(n)). However, due to the
|
|
||||||
* array-based nature of this class, insertion and removal of elements with anything but the largest
|
|
||||||
* key still require O(n) time.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public class ObservableSortedKeyedArrayList<K, E extends Keyed<? extends K>>
|
|
||||||
extends ObservableKeyedArrayList<K, E> implements ObservableSortedKeyedList<K, E> {
|
|
||||||
@Nullable private final Comparator<? super K> comparator;
|
|
||||||
private final transient KeyList<K, E> keyList = new KeyList<>(this);
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public ObservableSortedKeyedArrayList() {
|
|
||||||
comparator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableSortedKeyedArrayList(final Comparator<? super K> comparator) {
|
|
||||||
this.comparator = comparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableSortedKeyedArrayList(final Collection<? extends E> c) {
|
|
||||||
this();
|
|
||||||
addAll(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableSortedKeyedArrayList(final SortedKeyedList<K, E> other) {
|
|
||||||
this(other.comparator());
|
|
||||||
addAll(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean add(final E e) {
|
|
||||||
final int insertionPoint = getInsertionPoint(e);
|
|
||||||
if (insertionPoint < 0) {
|
|
||||||
// Skipping insertion is non-destructive if the new and existing objects are the same.
|
|
||||||
if (e == get(-insertionPoint - 1))
|
|
||||||
return false;
|
|
||||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
|
||||||
}
|
|
||||||
super.add(insertionPoint, e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(final int index, final E e) {
|
|
||||||
final int insertionPoint = getInsertionPoint(e);
|
|
||||||
if (insertionPoint < 0)
|
|
||||||
throw new IllegalArgumentException("Element with same key already exists in list");
|
|
||||||
if (insertionPoint != index)
|
|
||||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
|
||||||
super.add(index, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean addAll(final Collection<? extends E> c) {
|
|
||||||
boolean didChange = false;
|
|
||||||
for (final E e : c)
|
|
||||||
if (add(e))
|
|
||||||
didChange = true;
|
|
||||||
return didChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean addAll(int index, final Collection<? extends E> c) {
|
|
||||||
for (final E e : c)
|
|
||||||
add(index++, e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Comparator<? super K> comparator() {
|
|
||||||
return comparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public K firstKey() {
|
|
||||||
if (isEmpty())
|
|
||||||
// The parameter in the exception is only to shut
|
|
||||||
// lint up, we never care for the exception message.
|
|
||||||
throw new NoSuchElementException("Empty set");
|
|
||||||
return get(0).getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getInsertionPoint(final E e) {
|
|
||||||
if (comparator != null) {
|
|
||||||
return -Collections.binarySearch(keyList, e.getKey(), comparator) - 1;
|
|
||||||
} else {
|
|
||||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
|
||||||
(List<Comparable<? super K>>) keyList;
|
|
||||||
return -Collections.binarySearch(list, e.getKey()) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int indexOfKey(final K key) {
|
|
||||||
final int index;
|
|
||||||
if (comparator != null) {
|
|
||||||
index = Collections.binarySearch(keyList, key, comparator);
|
|
||||||
} else {
|
|
||||||
@SuppressWarnings("unchecked") final List<Comparable<? super K>> list =
|
|
||||||
(List<Comparable<? super K>>) keyList;
|
|
||||||
index = Collections.binarySearch(list, key);
|
|
||||||
}
|
|
||||||
return index >= 0 ? index : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<K> keySet() {
|
|
||||||
return keyList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int lastIndexOfKey(final K key) {
|
|
||||||
// There can never be more than one element with the same key in the list.
|
|
||||||
return indexOfKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public K lastKey() {
|
|
||||||
if (isEmpty())
|
|
||||||
// The parameter in the exception is only to shut
|
|
||||||
// lint up, we never care for the exception message.
|
|
||||||
throw new NoSuchElementException("Empty set");
|
|
||||||
return get(size() - 1).getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E set(final int index, final E e) {
|
|
||||||
final int order;
|
|
||||||
if (comparator != null) {
|
|
||||||
order = comparator.compare(e.getKey(), get(index).getKey());
|
|
||||||
} else {
|
|
||||||
@SuppressWarnings("unchecked") final Comparable<? super K> key =
|
|
||||||
(Comparable<? super K>) e.getKey();
|
|
||||||
order = key.compareTo(get(index).getKey());
|
|
||||||
}
|
|
||||||
if (order != 0) {
|
|
||||||
// Allow replacement if the new key would be inserted adjacent to the replaced element.
|
|
||||||
final int insertionPoint = getInsertionPoint(e);
|
|
||||||
if (insertionPoint < index || insertionPoint > index + 1)
|
|
||||||
throw new IndexOutOfBoundsException("Wrong index given for element");
|
|
||||||
}
|
|
||||||
return super.set(index, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<E> values() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class KeyList<K, E extends Keyed<? extends K>>
|
|
||||||
extends AbstractList<K> implements Set<K> {
|
|
||||||
private final ObservableSortedKeyedArrayList<K, E> list;
|
|
||||||
|
|
||||||
private KeyList(final ObservableSortedKeyedArrayList<K, E> list) {
|
|
||||||
this.list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public K get(final int index) {
|
|
||||||
return list.get(index).getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("EmptyMethod")
|
|
||||||
public Spliterator<K> spliterator() {
|
|
||||||
return super.spliterator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.android.util;
|
|
||||||
|
|
||||||
import com.wireguard.util.Keyed;
|
|
||||||
import com.wireguard.util.NonNullForAll;
|
|
||||||
import com.wireguard.util.SortedKeyedList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list that is both sorted/keyed and observable.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public interface ObservableSortedKeyedList<K, E extends Keyed<? extends K>>
|
|
||||||
extends ObservableKeyedList<K, E>, SortedKeyedList<K, E> {
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for objects that have a identifying key of the given type.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public interface Keyed<K> {
|
|
||||||
K getKey();
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.util;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list containing elements that can be looked up by key. A {@code KeyedList} cannot contain
|
|
||||||
* {@code null} elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public interface KeyedList<K, E extends Keyed<? extends K>> extends List<E> {
|
|
||||||
boolean containsAllKeys(Collection<K> keys);
|
|
||||||
|
|
||||||
boolean containsKey(K key);
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
E get(K key);
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
E getLast(K key);
|
|
||||||
|
|
||||||
int indexOfKey(K key);
|
|
||||||
|
|
||||||
int lastIndexOfKey(K key);
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.wireguard.util;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A keyed list where all elements are sorted by the comparator returned by {@code comparator()}
|
|
||||||
* applied to their keys.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@NonNullForAll
|
|
||||||
public interface SortedKeyedList<K, E extends Keyed<? extends K>> extends KeyedList<K, E> {
|
|
||||||
Comparator<? super K> comparator();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
K firstKey();
|
|
||||||
|
|
||||||
Set<K> keySet();
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
K lastKey();
|
|
||||||
|
|
||||||
Collection<E> values();
|
|
||||||
}
|
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="appData"
|
name="appData"
|
||||||
type="com.wireguard.android.util.ObservableKeyedList<String, ApplicationData>" />
|
type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ApplicationData>" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="collection"
|
name="collection"
|
||||||
type="com.wireguard.android.util.ObservableKeyedList<String, com.wireguard.android.model.ApplicationData>" />
|
type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, com.wireguard.android.model.ApplicationData>" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="key"
|
name="key"
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="tunnels"
|
name="tunnels"
|
||||||
type="com.wireguard.android.util.ObservableKeyedList<String, ObservableTunnel>" />
|
type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ObservableTunnel>" />
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="collection"
|
name="collection"
|
||||||
type="com.wireguard.android.util.ObservableKeyedList<String, ObservableTunnel>" />
|
type="com.wireguard.android.databinding.ObservableKeyedArrayList<String, ObservableTunnel>" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="key"
|
name="key"
|
||||||
|
Loading…
Reference in New Issue
Block a user