package org.commcare.fragments;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import org.commcare.CommCareApplication;
import org.commcare.activities.CommCareActivity;
import org.commcare.activities.CommCareSetupActivity;
import org.commcare.activities.FormRecordListActivity;
import org.commcare.cases.entity.Entity;
import org.commcare.cases.entity.NodeEntityFactory;
import org.commcare.dalvik.R;
import org.commcare.logging.AndroidLogger;
import org.commcare.models.AndroidSessionWrapper;
import org.commcare.preferences.DeveloperPreferences;
import org.commcare.session.CommCareSession;
import org.commcare.session.SessionFrame;
import org.commcare.suite.model.Detail;
import org.commcare.suite.model.EntityDatum;
import org.commcare.suite.model.StackFrameStep;
import org.commcare.utils.AndroidUtil;
import org.commcare.utils.SessionStateUninitException;
import org.commcare.views.EntityViewTile;
import org.commcare.views.TabbedDetailView;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.services.Logger;
import org.javarosa.core.util.NoLocalizedTextException;
import java.util.Vector;
/**
* @author ctsims
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class BreadcrumbBarFragment extends Fragment {
private TabbedDetailView mInternalDetailView = null;
private View tile;
/**
* This method will only be called once when the retained
* Fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Retain this fragment across configuration changes.
setRetainInstance(true);
}
/**
* Hold a reference to the parent Activity so we can report the task's
* current progress and results. The Android framework will pass us a
* reference to the newly created Activity after each configuration change.
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity) {
refresh((Activity)context);
} else {
Logger.log(AndroidLogger.SOFT_ASSERT, "Unable to attach breadcrumb bar fragment");
}
}
public void refresh(Activity activity) {
boolean breadCrumbsEnabled = !DeveloperPreferences.isActionBarEnabled();
ActionBar actionBar = activity.getActionBar();
if (!breadCrumbsEnabled) {
configureSimpleNav(activity, actionBar);
} else {
attachBreadcrumbBar(activity, actionBar);
}
this.tile = findAndLoadCaseTile(activity);
}
private void configureSimpleNav(Activity activity, ActionBar actionBar) {
boolean showNav = true;
if (activity instanceof CommCareActivity) {
showNav = ((CommCareActivity)activity).isBackEnabled();
}
if (showNav) {
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
actionBar.setDisplayShowTitleEnabled(true);
String title = getBestTitle(activity);
actionBar.setTitle(title);
}
private void attachBreadcrumbBar(Activity activity, ActionBar actionBar) {
//make sure we're in the right mode
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
//We need to get the amount that each item should "bleed" over to the left, and move the whole widget that
//many pixels. This replicates the "overlap" space that each piece of the bar has on the next piece for
//the left-most element.
int buffer = Math.round(activity.getResources().getDimension(R.dimen.title_round_bleed));
LayoutParams p = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
p.leftMargin = buffer;
activity.setTitle("");
actionBar.setDisplayShowHomeEnabled(false);
}
private static void expand(Activity activity, final View v) {
Display display = activity.getWindowManager().getDefaultDisplay();
if (activity instanceof CommCareActivity) {
((CommCareActivity)activity).setMainScreenBlocked(true);
}
int specHeight = MeasureSpec.makeMeasureSpec(display.getHeight(), MeasureSpec.AT_MOST);
v.measure(LayoutParams.MATCH_PARENT, specHeight);
final int targetHeight = v.getMeasuredHeight();
v.getLayoutParams().height = 0;
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)v.getLayoutParams();
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
v.setVisibility(View.VISIBLE);
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)v.getLayoutParams();
if (interpolatedTime == 1) {
lp.height = 0;
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1);
} else {
lp.height = (int)(targetHeight * interpolatedTime);
}
v.requestLayout();
}
@Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density) * 2);
v.startAnimation(a);
}
private static void collapse(Activity activity, final View v) {
if (activity instanceof CommCareActivity) {
((CommCareActivity)activity).setMainScreenBlocked(false);
}
final int initialHeight = v.getMeasuredHeight();
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)v.getLayoutParams();
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
lp.height = initialHeight;
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
v.setVisibility(View.GONE);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)v.getLayoutParams();
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1);
lp.height = 0;
} else {
v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
v.requestLayout();
}
}
@Override
public boolean willChangeBounds() {
return true;
}
};
// 1dp/ms
a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density) * 2);
v.startAnimation(a);
}
private View findAndLoadCaseTile(final Activity activity) {
final View holder = LayoutInflater.from(activity).inflate(R.layout.com_tile_holder, null);
final Pair<View, TreeReference> tileData = this.loadTile(activity);
if (tileData == null || tileData.first == null) {
return null;
}
View tile = tileData.first;
final String inlineDetail = (String)tile.getTag();
((ViewGroup)holder.findViewById(R.id.com_tile_holder_frame)).addView(tile, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
final ImageButton infoButton = ((ImageButton)holder.findViewById(R.id.com_tile_holder_btn_open));
if (inlineDetail == null) {
infoButton.setVisibility(View.GONE);
}
OnClickListener toggleButtonClickListener = new OnClickListener() {
private boolean isClosed = true;
@Override
public void onClick(View v) {
if (isClosed) {
if (mInternalDetailView == null) {
mInternalDetailView = (TabbedDetailView)holder.findViewById(R.id.com_tile_holder_detail_frame);
mInternalDetailView.setRoot(mInternalDetailView);
AndroidSessionWrapper asw = CommCareApplication.instance().getCurrentSessionWrapper();
CommCareSession session = asw.getSession();
Detail detail = session.getDetail(inlineDetail);
mInternalDetailView.showMenu();
mInternalDetailView.refresh(detail, tileData.second, 0);
}
expand(activity, holder.findViewById(R.id.com_tile_holder_detail_master));
infoButton.setImageResource(R.drawable.icon_info_fill_brandbg);
isClosed = false;
} else {
collapse(activity, holder.findViewById(R.id.com_tile_holder_detail_master));
infoButton.setImageResource(R.drawable.icon_info_outline_brandbg);
isClosed = true;
}
}
};
infoButton.setOnClickListener(toggleButtonClickListener);
return holder;
}
private Pair<View, TreeReference> loadTile(Activity activity) {
AndroidSessionWrapper asw;
try {
asw = CommCareApplication.instance().getCurrentSessionWrapper();
} catch (SessionStateUninitException e) {
return null;
}
CommCareSession session = asw.getSession();
StackFrameStep stepToFrame = null;
Vector<StackFrameStep> v = session.getFrame().getSteps();
//So we need to work our way backwards through each "step" we've taken, since our RelativeLayout
//displays the Z-Order b insertion (so items added later are always "on top" of items added earlier
for (int i = v.size() - 1; i >= 0; i--) {
StackFrameStep step = v.elementAt(i);
if (SessionFrame.STATE_DATUM_VAL.equals(step.getType())) {
//Only add steps which have a tile.
EntityDatum entityDatum = asw.getSession().findDatumDefinition(step.getId());
if (entityDatum != null && entityDatum.getPersistentDetail() != null) {
stepToFrame = step;
}
}
}
Pair<View, TreeReference> tile = buildContextTile(stepToFrame, asw);
//some contexts may provide a tile that isn't really part of the current session's stack
if (tile == null && activity instanceof CommCareActivity) {
Pair<Detail, TreeReference> entityContext = ((CommCareActivity)activity).requestEntityContext();
if (entityContext != null) {
tile = buildContextTile(entityContext.first, entityContext.second, asw);
}
}
return tile;
}
@Override
public void onResume() {
super.onResume();
if (tile != null) {
ViewGroup vg = (ViewGroup)this.getActivity().findViewById(R.id.universal_frame_tile);
//Check whether the view group is available. If so, this activity is a frame tile host
if (vg != null) {
if (tile.getParent() != null) {
((ViewGroup)tile.getParent()).removeView(tile);
}
vg.addView(tile, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
//this doesn't really make it over well
mInternalDetailView = null;
}
}
}
private static String getBestTitle(Activity activity) {
String bestTitle = getBestTitleHelper();
return defaultTitle(bestTitle, activity);
}
/**
* Unlike the main header, subheaders should not fall back to a default title
*/
public static String getBestSubHeaderTitle() {
return getBestTitleHelper();
}
private static String getBestTitleHelper() {
AndroidSessionWrapper asw;
try {
asw = CommCareApplication.instance().getCurrentSessionWrapper();
} catch (SessionStateUninitException e) {
return null;
}
CommCareSession session = asw.getSession();
String[] stepTitles;
try {
stepTitles = session.getHeaderTitles();
} catch (NoLocalizedTextException e) {
// localization resources may not be installed while in the middle
// of an update, so default to a generic title
return null;
}
Vector<StackFrameStep> v = session.getFrame().getSteps();
//So we need to work our way backwards through each "step" we've taken, since our RelativeLayout
//displays the Z-Order b insertion (so items added later are always "on top" of items added earlier
String bestTitle = null;
for (int i = v.size() - 1; i >= 0; i--) {
if (bestTitle != null) {
break;
}
StackFrameStep step = v.elementAt(i);
if (!SessionFrame.STATE_DATUM_VAL.equals(step.getType())) {
bestTitle = stepTitles[i];
}
}
return bestTitle;
}
private static String defaultTitle(String currentTitle, Activity activity) {
if (activity instanceof CommCareSetupActivity) {
return "CommCare";
}
if (currentTitle == null || "".equals(currentTitle)) {
currentTitle = CommCareActivity.getTopLevelTitleName(activity);
}
if (currentTitle == null || "".equals(currentTitle)) {
currentTitle = "CommCare";
}
if (activity instanceof FormRecordListActivity) {
currentTitle = currentTitle + " - " + ((FormRecordListActivity)activity).getActivityTitle();
}
return currentTitle;
}
private Pair<View, TreeReference> buildContextTile(StackFrameStep stepToFrame, AndroidSessionWrapper asw) {
if (stepToFrame == null) {
return null;
}
//check to make sure we can look up this child
EntityDatum entityDatum = asw.getSession().findDatumDefinition(stepToFrame.getId());
if (entityDatum == null || entityDatum.getPersistentDetail() == null) {
return null;
}
//Make sure there is a valid reference to the entity we can build
Detail detail = asw.getSession().getDetail(entityDatum.getPersistentDetail());
EvaluationContext ec = asw.getEvaluationContext();
TreeReference ref = entityDatum.getEntityFromID(ec, stepToFrame.getValue());
if (ref == null) {
return null;
}
Pair<View, TreeReference> r = buildContextTile(detail, ref, asw);
r.first.setTag(entityDatum.getInlineDetail());
return r;
}
private Pair<View, TreeReference> buildContextTile(Detail detail, TreeReference ref, AndroidSessionWrapper asw) {
NodeEntityFactory nef = new NodeEntityFactory(detail, asw.getEvaluationContext());
Entity entity = nef.getEntity(ref);
Log.v("DEBUG-v", "Creating new GridEntityView for text header text");
EntityViewTile tile = EntityViewTile.createTileForIndividualDisplay(this.getActivity(),
detail, entity);
int[] textColor = AndroidUtil.getThemeColorIDs(getActivity(),
new int[]{R.attr.drawer_pulldown_text_color, R.attr.menu_tile_title_text_color});
tile.setTextColor(textColor[0]);
tile.setTitleTextColor(textColor[1]);
return Pair.create(((View)tile), ref);
}
}