databinding: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
-162
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user