/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.util.SparseArrayCompat;
import android.view.View;
class ComponentHostUtils {
/**
* Moves an item from oldIndex to newIndex. The item is taken from scrapitems if an item exists
* in scrapItems at oldPosition. Otherwise the item is taken from items. This assumes that there
* is no item at newIndex for the items array. If that's the case
* {@link ComponentHostUtils#scrapItemAt(int, SparseArrayCompat, SparseArrayCompat)}
* has to be called before invoking this.
*/
static <T> void moveItem(
int oldIndex,
int newIndex,
SparseArrayCompat<T> items,
SparseArrayCompat<T> scrapItems) {
T itemToMove;
if (existsScrapItemAt(oldIndex, scrapItems)) {
// Before moving the item from items we need to check whether an old item has been put in
// the scrapItems array. If there is an item at oldIndex there, it means that in
// items at position oldIndex there's now something else and the correct item to move to
// newIndex is instead in the scrapItems SparseArray.
itemToMove = scrapItems.get(oldIndex);
scrapItems.remove(oldIndex);
} else {
itemToMove = items.get(oldIndex);
items.remove(oldIndex);
}
items.put(newIndex, itemToMove);
}
/**
* Takes the item at position index from items and puts it into scrapItems. If no such item exists
* the invocation of this method will have no effect.
*/
static <T> void scrapItemAt(
int index,
SparseArrayCompat<T> items,
SparseArrayCompat<T> scrapItems) {
final T value = items.get(index);
if (value != null) {
scrapItems.put(index, value);
}
}
/**
* Returns true if scrapItems is not null and contains an item with key index.
*/
static <T> boolean existsScrapItemAt(
int index,
SparseArrayCompat<T> scrapItems) {
return scrapItems != null && scrapItems.get(index) != null;
}
/**
* Sets the state on a drawable if it is clickable or should duplicate its parent's state.
*/
static void maybeSetDrawableState(View view, Drawable drawable, int flags, NodeInfo nodeInfo) {
final boolean shouldSetState = (nodeInfo != null && nodeInfo.hasTouchEventHandlers())
|| MountItem.isDuplicateParentState(flags);
if (shouldSetState && drawable.isStateful()) {
drawable.setState(view.getDrawableState());
}
}
/**
* Remove the item at given {@param index}. The item is removed from {@param scrapItems} if the
* item exists there at given index, otherwise it is removed from {@param items}.
*/
static <T> void removeItem(
int index,
SparseArrayCompat<T> items,
SparseArrayCompat<T> scrapItems) {
if (existsScrapItemAt(index, scrapItems)) {
scrapItems.remove(index);
} else {
items.remove(index);
}
}
/**
* Mounts a drawable into a view.
* @param view view into which the drawable should be mounted
* @param drawable drawable to be mounted
* @param bounds bounds of the drawable being mounted
* @param flags flags that determine whether the drawable obtains state from the view
* @param nodeInfo nodeInfo associated to the drawable node
*/
static void mountDrawable(
View view,
Drawable drawable,
Rect bounds,
int flags,
NodeInfo nodeInfo) {
drawable.setVisible(view.getVisibility() == View.VISIBLE, false);
drawable.setCallback(view);
maybeSetDrawableState(view, drawable, flags, nodeInfo);
view.invalidate(bounds);
}
static List<?> extractContent(SparseArrayCompat<MountItem> items) {
final int size = items.size();
if (size == 1) {
return Collections.singletonList(items.valueAt(0).getContent());
}
final List<Object> content = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
content.add(items.valueAt(i).getContent());
}
return content;
}
static TextContent extractTextContent(List<?> items) {
final int size = items.size();
if (size == 1) {
Object item = items.get(0);
return item instanceof TextContent ? (TextContent) item : TextContent.EMPTY;
}
final List<CharSequence> textContent = new ArrayList<>();
for (int i = 0; i < size; ++i) {
final Object item = items.get(i);
if (item instanceof TextContent) {
textContent.addAll(((TextContent) item).getTextItems());
}
}
return new TextContent() {
@Override
public List<CharSequence> getTextItems() {
return textContent;
}
};
}
static ImageContent extractImageContent(List<?> items) {
final int size = items.size();
if (size == 1) {
Object item = items.get(0);
return item instanceof ImageContent ? (ImageContent) item : ImageContent.EMPTY;
}
final List<Drawable> imageContent = new ArrayList<>();
for (int i = 0; i < size; ++i) {
final Object item = items.get(i);
if (item instanceof ImageContent) {
imageContent.addAll(((ImageContent) item).getImageItems());
}
}
return new ImageContent() {
@Override
public List<Drawable> getImageItems() {
return imageContent;
}
};
}
static void maybeInvalidateAccessibilityState(MountItem mountItem) {
if (mountItem.isAccessible()) {
mountItem.getHost().invalidateAccessibilityState();
}
}
/**
* Check whether {@param targetHost} is an ancestor of given {@param host} in the layout tree
*/
static boolean hasAncestorHost(ComponentHost host, ComponentHost targetHost) {
if (host == null) {
return false;
}
if (host == targetHost) {
return true;
}
if (!(host.getParent() instanceof ComponentHost)) {
return false;
}
return hasAncestorHost((ComponentHost) host.getParent(), targetHost);
}
}