package com.jasonette.seed.Section;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.jasonette.seed.Component.JasonComponentFactory;
import com.jasonette.seed.Helper.JasonHelper;
import com.jasonette.seed.Core.JasonViewActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/********************************************************
*
* Here's the hierarchy:
*
* - ViewHolder
* - ContentView
* - Layout
* - Component
*
********************************************************/
public class ItemAdapter extends RecyclerView.Adapter <ItemAdapter.ViewHolder>{
public static final int DATA = 0;
Context context;
Context root_context;
ArrayList<JSONObject> items;
ArrayList<JSONObject> cloned_items;
Map<String, Integer> signature_to_type = new HashMap<String,Integer>();
Map<Integer, String> type_to_signature = new HashMap<Integer, String>();
ViewHolderFactory factory = new ViewHolderFactory();
Boolean isHorizontalScroll = false;
/********************************************************
*
* Root level RecyclerView/ViewHolder logic
*
********************************************************/
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
ArrayList<View> subviews;
String type;
public ViewHolder(View itemView){
super(itemView);
this.subviews = new ArrayList<View>();
itemView.setOnClickListener(this);
this.type = "item";
}
public View getView(){
return this.itemView;
}
@Override
public void onClick(View view) {
int position = getAdapterPosition();
JSONObject item = (JSONObject)view.getTag();
try {
if (item.has("action")) {
JSONObject action = item.getJSONObject("action");
((JasonViewActivity)root_context).call(action.toString(), new JSONObject().toString(), "{}", view.getContext());
} else if (item.has("href")){
JSONObject href = item.getJSONObject("href");
JSONObject action = new JSONObject().put("type", "$href").put("options", href);
((JasonViewActivity)root_context).call(action.toString(), new JSONObject().toString(), "{}", view.getContext());
}
} catch (Exception e){ }
}
}
public ItemAdapter(Context root_context, Context context, ArrayList<JSONObject> items) {
this.items = items;
this.cloned_items = new ArrayList<JSONObject>();
this.cloned_items.addAll(items);
this.context = context;
this.root_context = root_context;
}
public void filter(String text) {
this.items.clear();
if(text.isEmpty()){
this.items.addAll(this.cloned_items);
} else{
text = text.toLowerCase();
for(JSONObject item: this.cloned_items){
if(item.toString().toLowerCase().contains(text)){
this.items.add(item);
}
}
}
notifyDataSetChanged();
}
@Override
// For determining the view type.
// 1. Generate a signature using the JSON markup and assign it to signature_to_type.
// 2. If the signature already exists, return the type.
public int getItemViewType(int position) {
JSONObject item = this.items.get(position);
// if the key starts with "horizontal_section",
// we deal with it in a special manner.
// Assuming that all items for a horizontal section will have the same prototype,
// we can generate the signature from just one of its items.
String stringified_item;
if(item.has("horizontal_section")){
try {
JSONArray horizontal_section_items = item.getJSONArray("horizontal_section");
// assuming that the section would contain at least one item,
// we will take the first item from the section and generate the signature
JSONObject first_item = horizontal_section_items.getJSONObject(0);
stringified_item = "[" + first_item.toString() + "]";
} catch (Exception e) {
stringified_item = item.toString();
}
} else {
stringified_item = item.toString();
}
// Simplistic way of transforming an item JSON into a generic string, by replacing out all non-structural values
// - replace out text and url
String regex = "\"(url|text)\"[ ]*:[ ]*\"([^\"]+)\"";
String signature = stringified_item.replaceAll(regex, "\"jason\":\"jason\"");
// - replace out 'title' and 'description'
regex = "\"(title|description)\"[ ]*:[ ]*\"([^\"]+)\"";
signature = signature.replaceAll(regex, "\"jason\":\"jason\"");
if(signature_to_type.containsKey(signature)){
// if the signature exists, get the type using the signature
return signature_to_type.get(signature);
} else {
// If it's a new signature, set the mapping between jason and type, both ways
// Increment the index (new type) first.
int index = signature_to_type.size();
// 1. jason => type: assign that index as the type for the signature
signature_to_type.put(signature, index);
// 2. type => jason: assign the stringified item so it can be used later
// Need to use the original instance instead of the stubbed out "signature" since some components requre url or text attributes to instantiate (create)
type_to_signature.put(index, stringified_item);
//type_to_signature.put(index, signature);
// Return the new index;
return index;
}
}
@Override
public ItemAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
String signatureString = type_to_signature.get(new Integer(viewType));
ItemAdapter.ViewHolder viewHolder;
if (signatureString.startsWith("[")) {
// Horizontal Section => Build a ViewHolder with a horizontally scrolling RecyclerView
// 1. Create RecyclerView
RecyclerView horizontalListView = new RecyclerView(parent.getContext());
horizontalListView.setLayoutManager(new LinearLayoutManager(horizontalListView.getContext(), LinearLayoutManager.HORIZONTAL, false));
horizontalListView.setNestedScrollingEnabled(false);
// 2. Create Adapter
ItemAdapter horizontal_adapter = new ItemAdapter(context, horizontalListView.getContext(), new ArrayList<JSONObject>());
horizontal_adapter.isHorizontalScroll = true;
// 3. Connect RecyclerView with Adapter
horizontalListView.setAdapter(horizontal_adapter);
// 4. Instantiate a new ViewHolder with the RecyclerView
viewHolder = new ViewHolder(horizontalListView);
} else {
// Vertcial Section => Regular ViewHolder
JSONObject json;
try {
json = new JSONObject(signatureString);
} catch (JSONException e) {
json = new JSONObject();
}
viewHolder = factory.build(null, json);
}
return viewHolder;
}
@Override
public void onBindViewHolder(ItemAdapter.ViewHolder viewHolder, int position) {
JSONObject json = this.items.get(position);
if(json.has("horizontal_section")) {
// Horizontal Section
// In this case, the viewHolder is a Recyclerview.
// We fetch the recyclerview from the viewholder (the viewholder's itemView is the recyclerview)
ItemAdapter horizontalListAdapter = ((ItemAdapter) ((RecyclerView)viewHolder.itemView).getAdapter());
// Transform JasonArray into ArrayList
try {
horizontalListAdapter.items = JasonHelper.toArrayList(((JSONArray)json.getJSONArray("horizontal_section")));
} catch (Exception e) { }
// Update viewholder
horizontalListAdapter.notifyDataSetChanged();
viewHolder.itemView.invalidate();
} else {
// Vertical section
// Build ViewHolder via ViewHolderFactory
factory.build(viewHolder, json);
}
}
@Override
public int getItemCount(){
return this.items.size();
}
/********************************************************
*
* ViewHolderFactory => Creates ViewHolders
*
********************************************************/
public class ViewHolderFactory {
// "subviews" =>
//
// store the DOM tree under viewHolder, so that it can be accessed easily inside onBindViewHolder, for example:
// viewHolder.subviews = [Image1, TextView1, Image2, TextView2, TextView3, TextView4];
// for(int i = 0 ; i < viewHolder.subviews.size() ; i++){
// View el = viewHolder.subviews.get(i);
// if(el instancof Button){
// ..
// } ..
// }
private ArrayList<View> subviews;
private Boolean exists;
private int index;
public ItemAdapter.ViewHolder build(ViewHolder prototype, JSONObject json) {
LinearLayout layout;
if (prototype != null) {
// Fill
this.exists = true;
} else {
this.exists = false;
}
if (this.exists) {
// Fill
// Get the subviews
this.subviews = prototype.subviews;
this.index = 0;
// Build content view with the existing prototype layout
layout = (LinearLayout) prototype.getView();
buildContentView(layout, json);
layout.setTag(json);
// return the existing prototype layout
return prototype;
} else {
// Create
// Initialize subviews
this.subviews = new ArrayList<View>();
// Build content view with a new layout
layout = buildContentView(new LinearLayout(context), json);
// Create a new viewholder with the new layout
ItemAdapter.ViewHolder viewHolder = new ItemAdapter.ViewHolder(layout);
// Assign subviews
viewHolder.subviews = this.subviews;
return viewHolder;
}
}
// ContentView is the top level view of a cell.
// It's always a layout.
// If the JSON supplies a component, ContentView creates a layout wrapper around it
private LinearLayout buildContentView(LinearLayout layout, JSONObject json) {
try {
if (json.has("type")) {
String type = json.getString("type");
if (type.equalsIgnoreCase("vertical") || type.equalsIgnoreCase("horizontal")) {
layout = buildLayout(layout, json, null, 0);
layout.setClickable(true);
} else {
// 1. Create components array
JSONArray components = new JSONArray();
// 2. Create a vertical layout and set its components
JSONObject wrapper = new JSONObject();
wrapper.put("type", "vertical");
// When wrapping, we set the padding on the wrapper to 0, since it will be taken care of on the component
JSONObject style = new JSONObject();
style.put("padding", type.equalsIgnoreCase("html") ? 1 : 0);
wrapper.put("style", style);
// Instead, we set the component's padding to 10
JSONObject componentStyle;
if(json.has("style")) {
componentStyle = json.getJSONObject("style");
if(!componentStyle.has("padding")){
componentStyle.put("padding", "10");
}
} else {
componentStyle = new JSONObject();
componentStyle.put("padding", "10");
}
json.put("style", componentStyle);
// Setup components array
components.put(json);
wrapper.put("components", components);
// Setup href and actions
if (json.has("href")) {
wrapper.put("href", json.getJSONObject("href"));
}
if (json.has("action")) {
wrapper.put("action", json.getJSONObject("action"));
}
// 3. Start running the layout logic
buildLayout(layout, wrapper, null, 0);
// In case we're at the root level
// and the child has a width, we need to set the wrapper's width to wrap its child. (for horizontal scrolling sections)
View componentView = layout.getChildAt(0);
ViewGroup.LayoutParams componentLayoutParams = (ViewGroup.LayoutParams)componentView.getLayoutParams();
if(componentLayoutParams.width > 0){
ViewGroup.LayoutParams layoutParams = (ViewGroup.LayoutParams)layout.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
}
}
} else {
layout = new LinearLayout(context);
}
} catch (JSONException e) {
layout = new LinearLayout(context);
}
return layout;
}
public LinearLayout buildLayout(LinearLayout layout, JSONObject item, JSONObject parent, int level) {
if (exists) {
try {
JSONArray components = item.getJSONArray("components");
for (int i = 0; i < components.length(); i++) {
JSONObject component = components.getJSONObject(i);
if (component.getString("type").equalsIgnoreCase("vertical") || component.getString("type").equalsIgnoreCase("horizontal")) {
LinearLayout childLayout = (LinearLayout)layout.getChildAt(i);
buildLayout(childLayout, component, item, ++level);
if (i > 0) {
add_spacing(childLayout, item, item.getString("type"));
}
} else {
View child_component = buildComponent(component, item);
if (i > 0) {
add_spacing(child_component, item, item.getString("type"));
}
}
}
} catch (JSONException e) {
}
return new LinearLayout(context);
} else {
try {
// Layout styling
String type = item.getString("type");
JSONObject style = JasonHelper.style(item, root_context);
JSONArray components;
if (type.equalsIgnoreCase("vertical") || type.equalsIgnoreCase("horizontal")) {
components = item.getJSONArray("components");
} else {
components = new JSONArray();
}
LinearLayout.LayoutParams layoutParams;
if (type.equalsIgnoreCase("vertical")) {
// vertical layout
layout.setOrientation(LinearLayout.VERTICAL);
components = item.getJSONArray("components");
} else if (type.equalsIgnoreCase("horizontal")) {
// horizontal layout
layout.setOrientation(LinearLayout.HORIZONTAL);
components = item.getJSONArray("components");
}
// set width and height
layoutParams = JasonLayout.autolayout(isHorizontalScroll, parent, item, root_context);
layout.setLayoutParams(layoutParams);
// Padding
// If root level, set the default padding to 10
String default_padding;
if (level == 0) {
default_padding = "10";
} else {
default_padding = "0";
}
int padding_left = (int) JasonHelper.pixels(root_context, default_padding, type);
int padding_right = (int) JasonHelper.pixels(root_context, default_padding, type);
int padding_top = (int) JasonHelper.pixels(root_context, default_padding, type);
int padding_bottom = (int) JasonHelper.pixels(root_context, default_padding, type);
if (style.has("padding")) {
padding_left = (int) JasonHelper.pixels(root_context, style.getString("padding"), type);
padding_right = padding_left;
padding_top = padding_left;
padding_bottom = padding_left;
}
if (style.has("padding_left")) {
padding_left = (int) JasonHelper.pixels(root_context, style.getString("padding_left"), type);
}
if (style.has("padding_right")) {
padding_right = (int) JasonHelper.pixels(context, style.getString("padding_right"), type);
}
if (style.has("padding_top")) {
padding_top = (int) JasonHelper.pixels(root_context, style.getString("padding_top"), type);
}
if (style.has("padding_bottom")) {
padding_bottom = (int) JasonHelper.pixels(root_context, style.getString("padding_bottom"), type);
}
layout.setPadding(padding_left, padding_top, padding_right, padding_bottom);
// spacing
for (int i = 0; i < components.length(); i++) {
JSONObject component = components.getJSONObject(i);
String component_type = component.getString("type");
if (component_type.equalsIgnoreCase("vertical") || component_type.equalsIgnoreCase("horizontal")) {
// the child is also a layout
LinearLayout child_layout = buildLayout(new LinearLayout(context), component, item, ++level);
layout.addView(child_layout);
if (i > 0) {
add_spacing(child_layout, item, type);
}
// From item1, start adding margin-top (item0 shouldn't have margin-top)
} else {
View child_component = buildComponent(component, item);
// the child is a leaf node
layout.addView(child_component);
if (i > 0) {
add_spacing(child_component, item, type);
}
}
}
// align
if (style.has("align")) {
if (style.getString("align").equalsIgnoreCase("center")) {
layout.setGravity(Gravity.CENTER);
} else if (style.getString("align").equalsIgnoreCase("right")) {
layout.setGravity(Gravity.RIGHT);
} else {
layout.setGravity(Gravity.LEFT);
}
}
// background
if (style.has("background")) {
layout.setBackgroundColor(JasonHelper.parse_color(style.getString("background")));
}
layout.requestLayout();
} catch (JSONException e) {
}
return layout;
}
}
public View buildComponent(JSONObject component, JSONObject parent) {
View view;
JSONObject style = JasonHelper.style(component, root_context);
if (exists) {
view = (View) this.subviews.get(this.index++);
JasonComponentFactory.build(view, component, parent, root_context);
return view;
} else {
view = JasonComponentFactory.build(null, component, parent, root_context);
view.setId(this.subviews.size());
this.subviews.add(view);
return view;
}
}
private void add_spacing(View view, JSONObject item, String type) {
try {
String spacing = "0";
JSONObject style = JasonHelper.style(item, root_context);
if (style.has("spacing")) {
spacing = style.getString("spacing");
} else {
spacing = "0";
}
if (type.equalsIgnoreCase("vertical")) {
int m = (int) JasonHelper.pixels(context, spacing, item.getString("type"));
LinearLayout.LayoutParams layoutParams;
if(view.getLayoutParams() == null) {
layoutParams = new LinearLayout.LayoutParams(0,0);
} else {
layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
}
layoutParams.topMargin = m;
layoutParams.bottomMargin = 0;
view.setLayoutParams(layoutParams);
} else if (type.equalsIgnoreCase("horizontal")) {
int m = (int) JasonHelper.pixels(root_context, spacing, item.getString("type"));
LinearLayout.LayoutParams layoutParams;
if(view.getLayoutParams() == null) {
layoutParams = new LinearLayout.LayoutParams(0,0);
} else {
layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
}
layoutParams.leftMargin = m;
layoutParams.rightMargin = 0;
view.setLayoutParams(layoutParams);
}
view.requestLayout();
} catch (Exception e) {
Log.d("Error", e.toString());
}
}
}
}