/*
* Copyright 2016 Flipkart Internet Pvt. Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flipkart.android.proteus.view.manager;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.flipkart.android.proteus.DataContext;
import com.flipkart.android.proteus.binding.Binding;
import com.flipkart.android.proteus.builder.LayoutBuilder;
import com.flipkart.android.proteus.parser.LayoutHandler;
import com.flipkart.android.proteus.toolbox.ProteusConstants;
import com.flipkart.android.proteus.toolbox.Result;
import com.flipkart.android.proteus.toolbox.Styles;
import com.flipkart.android.proteus.toolbox.Utils;
import com.flipkart.android.proteus.view.ProteusView;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.ArrayList;
/**
* ProteusViewManagerImpl
*
* @author aditya.sharat
*/
public class ProteusViewManagerImpl implements ProteusViewManager {
private static final String TAG = "ProteusViewManagerImpl";
private View view;
private JsonObject layout;
private Styles styles;
private DataContext dataContext;
private LayoutBuilder layoutBuilder;
private LayoutHandler layoutHandler;
private OnUpdateDataListener onUpdateDataListener;
private String dataPathForChildren;
private JsonObject childLayout;
private boolean isViewUpdating;
private ArrayList<Binding> bindings;
@Override
public void update(@Nullable JsonObject data) {
if (ProteusConstants.isLoggingEnabled()) {
Log.d(TAG, "START: update data " + (data != null ? "(top-level)" : "") + "for view with " + Utils.getLayoutIdentifier(layout));
}
this.isViewUpdating = true;
data = onBeforeUpdateData(data);
// update the data context so all child views can refer to new data
if (data != null) {
updateDataContext(data);
}
data = onAfterDataContext(dataContext.getData());
// update the bindings of this view
if (this.bindings != null) {
for (Binding binding : this.bindings) {
this.handleBinding(binding);
}
}
// update the child views
if (view instanceof ViewGroup) {
if (dataPathForChildren != null) {
updateChildrenFromData();
} else {
ViewGroup parent = (ViewGroup) view;
View child;
int childCount = parent.getChildCount();
for (int index = 0; index < childCount; index++) {
child = parent.getChildAt(index);
if (child instanceof ProteusView) {
((ProteusView) child).getViewManager().update(dataContext.getData());
}
}
}
}
this.isViewUpdating = false;
if (ProteusConstants.isLoggingEnabled()) {
Log.d(TAG, "END: update data " + (data != null ? "(top-level)" : "") + "for view with " + Utils.getLayoutIdentifier(layout));
}
onUpdateDataComplete(dataContext.getData());
}
@Override
public void setView(View view) {
this.view = view;
}
private void updateDataContext(JsonObject data) {
if (dataContext.isClone()) {
dataContext.setData(data);
} else {
dataContext.updateDataContext(data);
}
}
private void updateChildrenFromData() {
JsonArray dataList = new JsonArray();
ViewGroup parent = ((ViewGroup) view);
Result result = Utils.readJson(dataPathForChildren, dataContext.getData(), dataContext.getIndex());
if (result.isSuccess() && null != result.element && result.element.isJsonArray()) {
dataList = result.element.getAsJsonArray();
}
int childCount = parent.getChildCount();
View child;
if (childCount > dataList.size()) {
while (childCount > dataList.size()) {
childCount--;
child = parent.getChildAt(childCount);
if (child instanceof ProteusView) {
((ProteusView) child).getViewManager().destroy();
}
parent.removeViewAt(childCount);
}
}
JsonObject data = dataContext.getData();
ProteusView childView;
childCount = parent.getChildCount();
for (int index = 0; index < dataList.size(); index++) {
if (index < childCount) {
child = parent.getChildAt(index);
if (child instanceof ProteusView) {
((ProteusView) child).getViewManager().update(data);
}
} else if (childLayout != null) {
childView = layoutBuilder.build(parent, childLayout, data, dataContext.getIndex(), styles);
layoutHandler.addView((ProteusView) view, childView);
}
}
}
@Nullable
private JsonObject onBeforeUpdateData(JsonObject data) {
if (onUpdateDataListener != null) {
JsonObject override = onUpdateDataListener.onBeforeUpdateData(data);
if (override != null) {
return override;
}
}
return data;
}
@Nullable
private JsonObject onAfterDataContext(JsonObject data) {
if (onUpdateDataListener != null) {
JsonObject override = onUpdateDataListener.onAfterDataContext(data);
if (override != null) {
return override;
}
}
return data;
}
private void onUpdateDataComplete(JsonObject data) {
if (onUpdateDataListener != null) {
onUpdateDataListener.onUpdateDataComplete(data);
}
}
private void handleBinding(Binding binding) {
if (binding.hasRegEx()) {
layoutBuilder.handleAttribute(layoutHandler, (ProteusView) view, binding.getAttributeKey(), new JsonPrimitive(binding.getAttributeValue()));
} else {
Result result = Utils.readJson(binding.getBindingName(), dataContext.getData(), dataContext.getIndex());
JsonElement dataValue = result.isSuccess() ? result.element : JsonNull.INSTANCE;
layoutBuilder.handleAttribute(layoutHandler, (ProteusView) view, binding.getAttributeKey(), dataValue);
}
}
@Override
public LayoutBuilder getLayoutBuilder() {
return layoutBuilder;
}
@Override
public void setLayoutBuilder(LayoutBuilder layoutBuilder) {
this.layoutBuilder = layoutBuilder;
}
@Override
public LayoutHandler getLayoutHandler() {
return layoutHandler;
}
@Override
public void setLayoutHandler(LayoutHandler layoutHandler) {
this.layoutHandler = layoutHandler;
}
@Override
public JsonObject getLayout() {
return layout;
}
@Override
public void setLayout(JsonObject layout) {
this.layout = layout;
}
@Nullable
@Override
public Styles getStyles() {
return styles;
}
@Override
public void setStyles(@Nullable Styles styles) {
this.styles = styles;
}
@Override
public int getUniqueViewId(String id) {
return layoutBuilder.getUniqueViewId(id);
}
public JsonElement get(String dataPath, int index) {
return dataContext.get(dataPath);
}
public void set(String dataPath, JsonElement newValue) {
if (dataPath == null) {
return;
}
String aliasedDataPath = DataContext.getAliasedDataPath(dataPath, dataContext.getReverseScope(), true);
Result result = Utils.readJson(aliasedDataPath.substring(0, aliasedDataPath.lastIndexOf(".")), dataContext.getData(), dataContext.getIndex());
JsonElement parent = result.isSuccess() ? result.element : null;
if (parent == null || !parent.isJsonObject()) {
return;
}
String propertyName = aliasedDataPath.substring(aliasedDataPath.lastIndexOf(".") + 1, aliasedDataPath.length());
parent.getAsJsonObject().add(propertyName, newValue);
update(aliasedDataPath);
}
public void set(String dataPath, String newValue) {
set(dataPath, new JsonPrimitive(newValue));
}
public void set(String dataPath, Number newValue) {
set(dataPath, new JsonPrimitive(newValue));
}
public void set(String dataPath, boolean newValue) {
set(dataPath, new JsonPrimitive(newValue));
}
protected void update(String dataPath) {
this.isViewUpdating = true;
if (this.bindings != null) {
for (Binding binding : this.bindings) {
if (binding.getBindingName().equals(dataPath)) {
this.handleBinding(binding);
}
}
}
if (view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view;
int childCount = parent.getChildCount();
View child;
String aliasedDataPath;
for (int index = 0; index < childCount; index++) {
child = parent.getChildAt(index);
if (child instanceof ProteusView) {
aliasedDataPath = DataContext.getAliasedDataPath(dataPath, dataContext.getReverseScope(), false);
((ProteusViewManagerImpl) ((ProteusView) child).getViewManager()).update(aliasedDataPath);
}
}
}
this.isViewUpdating = false;
}
@Nullable
@Override
public JsonObject getChildLayout() {
return childLayout;
}
public void setChildLayout(@Nullable JsonObject childLayout) {
this.childLayout = childLayout;
}
@Override
public DataContext getDataContext() {
return dataContext;
}
@Override
public void setDataContext(DataContext dataContext) {
this.dataContext = dataContext;
}
@Nullable
@Override
public String getDataPathForChildren() {
return dataPathForChildren;
}
public void setDataPathForChildren(@Nullable String dataPathForChildren) {
this.dataPathForChildren = dataPathForChildren;
}
public boolean isViewUpdating() {
return isViewUpdating;
}
@Override
public void addBinding(Binding binding) {
if (this.bindings == null) {
this.bindings = new ArrayList<>();
}
bindings.add(binding);
}
@Override
public void destroy() {
view = null;
layout = null;
childLayout = null;
styles = null;
layoutBuilder = null;
layoutHandler = null;
onUpdateDataListener = null;
dataPathForChildren = null;
bindings = null;
}
@Override
public void setOnUpdateDataListener(@Nullable OnUpdateDataListener listener) {
this.onUpdateDataListener = listener;
}
@Override
public void removeOnUpdateDataListener() {
onUpdateDataListener = null;
}
@Nullable
@Override
public OnUpdateDataListener getOnUpdateDataListeners() {
return onUpdateDataListener;
}
}