package org.ohmage.controls; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import org.ohmage.R; import org.ohmage.logprobe.Analytics; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; public class DateFilterControl extends LinearLayout { public final static int defaultCalendarUnit = 0; private DateFilterChangeListener mFilterChangeListener; private Calendar mSelectedDate; private Button mCurrentBtn; private Button mPrevBtn; private Button mNextBtn; private final Activity mActivity; // stores a reference to our calling activity private AlertDialog mItemListDialog; // stores a dialog containing a list of items, updated by populate() and add() private SimpleDateFormat mDateFormatter; // determines the number of items to show before and after the current date // e.g. 6 ends up being 13 items total: 6 months before, the current month, and 6 months after private final int PICKER_RANGE = 3; // determines the unit that the filter pages through; the date string formatter is set accordingly private int CALENDAR_UNIT = Calendar.MONTH; // determine if "today" is an option at the top of the list selector private final boolean SHOW_TODAY_IN_PICKER = true; public DateFilterControl(Context context) { super(context); mActivity = (Activity)context; // just construct the base control initControl(context); } public DateFilterControl(Context context, AttributeSet attrs) { super(context, attrs); mActivity = (Activity)context; // construct the base control initControl(context); // apply the xml-specified attributes, too initStyles(attrs); } /** * Constructs the parts of the control that provide actual functionality. Declarative styling is handled by initStyles(). * @param context the context of whoever's creating this control, usually passed into the class constructor */ protected void initControl(Context context) { LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); this.setLayoutParams(params); this.setOrientation(HORIZONTAL); this.setBackgroundResource(R.color.lightergray); // load up the elements of the actionbar from controls_filter.xml LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.controls_filter, this, true); if(mSelectedDate == null) mSelectedDate = Calendar.getInstance(); mPrevBtn = (Button) findViewById(R.id.controls_filter_prev); mCurrentBtn = (Button) findViewById(R.id.controls_filter_current); mNextBtn = (Button) findViewById(R.id.controls_filter_next); FilterClickHandler handler = new FilterClickHandler(); mPrevBtn.setOnClickListener(handler); mCurrentBtn.setOnClickListener(handler); mNextBtn.setOnClickListener(handler); mCurrentBtn.setSelected(true); // make a simple date formatter to display the thing (this may change depending on the selection) switch (CALENDAR_UNIT) { case Calendar.MONTH: mDateFormatter = new SimpleDateFormat("MMMM yyyy"); break; case Calendar.YEAR: mDateFormatter = new SimpleDateFormat("yyyy"); break; default: mDateFormatter = new SimpleDateFormat("M/dd/yyyy"); } // also update the list dialog once (just going from current date) syncState(); updateListDialog(); } /** * Initializes the appearance of the control based on the attributes passed from the xml. * @param attrs the collection of attributes, usually provided in the control's constructor */ protected void initStyles(AttributeSet attrs) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ActivityBarControl); } /** * Sets the currently selected date. * @param newdate the date to select */ public void setDate(Calendar newDate) { mSelectedDate = newDate; syncState(); } /** * Gets the displayed text for the currently selected item (i.e. the text representation of the date) * @return the displayed text as a string */ public String getText() { return mSelectedDate.toString(); } /** * Gets the defined value for the currently selected item (i.e. the actual Calendar object) * @return the value as a Calendar */ public Calendar getValue() { return mSelectedDate; } /** * Attaches a {@link DateFilterChangeListener} to the filter which will be called when the user navigates between items. * * @param listener an object implementing {@link DateFilterChangeListener} which will be called when the list index is changed. */ public void setOnChangeListener(DateFilterChangeListener listener) { mFilterChangeListener = listener; } /** * Exposes a callback to allow custom processing to occur when the filter is changed (either by navigation or population). */ public static interface DateFilterChangeListener { public void onFilterChanged(Calendar curValue); } /** * Handles the next, previous, and current buttons in the view. * * @param v the view which generated the click; this method uses the id of the view to determine what to do */ private class FilterClickHandler implements OnClickListener { @Override public void onClick(View v) { Analytics.widget(v); switch (v.getId()) { case R.id.controls_filter_prev: mSelectedDate.add(CALENDAR_UNIT, -1); syncState(); break; case R.id.controls_filter_current: // the dialog changes with the current selection, so update it before we display it updateListDialog(); mItemListDialog.show(); break; case R.id.controls_filter_next: mSelectedDate.add(CALENDAR_UNIT, 1); syncState(); break; } } } // keeps the middle text button in sync with the current index private void syncState() { mCurrentBtn.setText(mDateFormatter.format(mSelectedDate.getTime())); if (mFilterChangeListener != null) mFilterChangeListener.onFilterChanged(mSelectedDate); } // helper method for constructing a list dialog based on the current item list private void updateListDialog() { // RANGE months before, current month, RANGE after, optional "today" final CharSequence items[]; if (SHOW_TODAY_IN_PICKER) items = new CharSequence[PICKER_RANGE*2 + 2]; else items = new CharSequence[PICKER_RANGE*2 + 1]; int idx = 0; for (int i = -PICKER_RANGE; i <= PICKER_RANGE; i++) { Calendar curDate = (Calendar)mSelectedDate.clone(); curDate.add(CALENDAR_UNIT, i); items[idx++] = mDateFormatter.format(curDate.getTime()); } if (SHOW_TODAY_IN_PICKER) items[idx++] = "Today"; // and construct a dialog that displays the list AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); builder.setTitle("Choose a date"); builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { try { if (items[item].equals("Today")) mSelectedDate = Calendar.getInstance(); else mSelectedDate.setTime(mDateFormatter.parse(items[item].toString())); syncState(); } catch (ParseException e) { // just ignore their selection, i guess? } } }); mItemListDialog = builder.create(); } // utility method for converting dp to pixels, since the setters only take pixel values :\ private int dpToPixels(int padding_in_dp) { final float scale = getResources().getDisplayMetrics().density; return (int) (padding_in_dp * scale + 0.5f); } public boolean setCalendarUnit(int unit){ if(CALENDAR_UNIT != unit) { CALENDAR_UNIT = unit; this.removeAllViews(); initControl(mActivity); return true; } return false; } public void setMonth(int month, int year) { setCalendarUnit(Calendar.MONTH); if(month != -1) mSelectedDate.set(Calendar.MONTH, month); if(year != -1) mSelectedDate.set(Calendar.YEAR, year); syncState(); } public void setDate(int day, int month, int year) { setCalendarUnit(Calendar.DATE); if(day != -1) mSelectedDate.set(Calendar.DATE, day); if(month != -1) mSelectedDate.set(Calendar.MONTH, month); if(year != -1) mSelectedDate.set(Calendar.YEAR, year); syncState(); } }