package org.odk.collect.android.widgets;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.javarosa.core.model.data.DateData;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.form.api.FormEntryPrompt;
import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.chrono.EthiopicChronology;
import org.joda.time.chrono.GregorianChronology;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.odk.collect.android.R;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
/**
* Ethiopian Date Widget.
*
* @author Alex Little (alex@alexlittle.net)
*/
public class EthiopianDateWidget extends QuestionWidget{
private TextView txtMonth;
private TextView txtDay;
private TextView txtYear;
private TextView txtGregorian;
private static Chronology chron_eth = EthiopicChronology.getInstance();
private String[] monthsArray;
private int ethiopianMonthArrayPointer;
private Button btnDayUp;
private Button btnMonthUp;
private Button btnYearUp;
private Button btnDayDown;
private Button btnMonthDown;
private Button btnYearDown;
private ScheduledExecutorService mUpdater;
private Handler mDayHandler;
private Handler mMonthHandler;
private Handler mYearHandler;
private static final int MSG_INC = 0;
private static final int MSG_DEC = 1;
// Alter this to make the button more/less sensitive to an initial long press
private static final int INITIAL_DELAY = 500;
// Alter this to vary how rapidly the date increases/decreases on long press
private static final int PERIOD = 200;
private class UpdateTask implements Runnable {
private boolean mInc;
private Handler mHandler;
public UpdateTask(boolean inc, Handler h) {
mInc = inc;
mHandler = h;
}
public void run() {
if (mInc) {
mHandler.sendEmptyMessage(MSG_INC);
} else {
mHandler.sendEmptyMessage(MSG_DEC);
}
}
}
/**
* Constructor method
* @param context
* @param prompt
*/
public EthiopianDateWidget(Context context, FormEntryPrompt prompt) {
super(context, prompt);
Resources res = getResources();
// load the months - will automatically get correct strings for current phone locale
monthsArray = res.getStringArray(R.array.ethiopian_months);
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View vv = vi.inflate(R.layout.ethiopian_date_widget, null);
addView(vv);
/*
* Initialise handlers for incrementing/decrementing dates
*/
mDayHandler = new Handler() {
/*
* (non-Javadoc)
* @see android.os.Handler#handleMessage(android.os.Message)
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INC:
incrementDay();
return;
case MSG_DEC:
decrementDay();
return;
}
super.handleMessage(msg);
}
};
mMonthHandler = new Handler() {
/*
* (non-Javadoc)
* @see android.os.Handler#handleMessage(android.os.Message)
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INC:
incrementMonth();
return;
case MSG_DEC:
decrementMonth();
return;
}
super.handleMessage(msg);
}
};
mYearHandler = new Handler() {
/*
* (non-Javadoc)
* @see android.os.Handler#handleMessage(android.os.Message)
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INC:
incrementYear();
return;
case MSG_DEC:
decrementYear();
return;
}
super.handleMessage(msg);
}
};
// Date fields
txtDay = (TextView) findViewById(R.id.daytxt);
txtMonth = (TextView) findViewById(R.id.monthtxt);
txtYear = (TextView) findViewById(R.id.yeartxt);
txtGregorian = (TextView) findViewById(R.id.dateGregorian);
// action buttons
btnDayUp = (Button) findViewById(R.id.dayupbtn);
btnMonthUp = (Button) findViewById(R.id.monthupbtn);
btnYearUp = (Button) findViewById(R.id.yearupbtn);
btnDayDown = (Button) findViewById(R.id.daydownbtn);
btnMonthDown = (Button) findViewById(R.id.monthdownbtn);
btnYearDown = (Button) findViewById(R.id.yeardownbtn);
// button click listeners
btnDayUp.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
incrementDay();
}
}
});
btnMonthUp.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
incrementMonth();
}
}
});
btnYearUp.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
incrementYear();
}
}
});
btnDayDown.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
decrementDay();
}
}
});
btnMonthDown.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
decrementMonth();
}
}
});
btnYearDown.setOnClickListener(new View.OnClickListener() {
/*
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View v) {
if (mUpdater == null) {
decrementYear();
}
}
});
// button touch listeners
btnDayUp.setOnTouchListener(new EDWTouchListener(btnDayUp,mDayHandler));
btnDayDown.setOnTouchListener(new EDWTouchListener(btnDayUp,mDayHandler));
btnMonthUp.setOnTouchListener(new EDWTouchListener(btnMonthUp,mMonthHandler));
btnMonthDown.setOnTouchListener(new EDWTouchListener(btnMonthUp,mMonthHandler));
btnYearUp.setOnTouchListener(new EDWTouchListener(btnYearUp,mYearHandler));
btnYearDown.setOnTouchListener(new EDWTouchListener(btnYearUp,mYearHandler));
// button key listeners
btnDayUp.setOnKeyListener(new EDWKeyListener(btnDayUp,mDayHandler));
btnDayDown.setOnKeyListener(new EDWKeyListener(btnDayUp,mDayHandler));
btnMonthUp.setOnKeyListener(new EDWKeyListener(btnMonthUp,mMonthHandler));
btnMonthDown.setOnKeyListener(new EDWKeyListener(btnMonthUp,mMonthHandler));
btnYearUp.setOnKeyListener(new EDWKeyListener(btnYearUp,mYearHandler));
btnYearDown.setOnKeyListener(new EDWKeyListener(btnYearUp,mYearHandler));
// If there's an answer, use it.
setAnswer();
}
/*
* (non-Javadoc)
* @see org.odk.collect.android.widgets.QuestionWidget#clearAnswer()
* Resets date to today
*/
@Override
public void clearAnswer() {
DateTime dt = new DateTime();
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/*
* (non-Javadoc)
* @see org.odk.collect.android.widgets.QuestionWidget#getAnswer()
* Return the date for storing in ODK
*/
@Override
public IAnswerData getAnswer() {
DateTime dt = getDateAsGregorian();
return new DateData(dt.toDate());
}
/*
* (non-Javadoc)
* @see org.odk.collect.android.widgets.QuestionWidget#setFocus(android.content.Context)
*/
@Override
public void setFocus(Context context) {
// Hide the soft keyboard if it's showing.
InputMethodManager inputManager =
(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(this.getWindowToken(), 0);
}
/*
* (non-Javadoc)
* @see org.odk.collect.android.widgets.QuestionWidget#setOnLongClickListener(android.view.View.OnLongClickListener)
*/
@Override
public void setOnLongClickListener(OnLongClickListener l) {
//super.setOnLongClickListener(l);
}
/*
* (non-Javadoc)
* @see org.odk.collect.android.widgets.QuestionWidget#cancelLongPress()
*/
@Override
public void cancelLongPress() {
super.cancelLongPress();
}
/**
* Start Updater, for when using long press to increment/decrement date without repeated pressing on the buttons
* @param inc
* @param mHandler
*/
private void startUpdating(boolean inc, Handler mHandler) {
if (mUpdater != null) {
Log.e(getClass().getSimpleName(), "Another executor is still active");
return;
}
mUpdater = Executors.newSingleThreadScheduledExecutor();
mUpdater.scheduleAtFixedRate(new UpdateTask(inc,mHandler), INITIAL_DELAY, PERIOD,
TimeUnit.MILLISECONDS);
}
/**
* Stop incrementing/decrementing
*/
private void stopUpdating() {
mUpdater.shutdownNow();
mUpdater = null;
}
/**
* Increase by 1 day
*/
private void incrementDay(){
// get the current date into gregorian, add one and redisplay
DateTime dt = getDateAsGregorian().plusDays(1);
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Increase by 1 month
*/
private void incrementMonth(){
DateTime dt = getCurrentEthiopianDateDisplay().plusMonths(1).withChronology(GregorianChronology.getInstance());
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Increase by 1 year
*/
private void incrementYear(){
DateTime dt = getCurrentEthiopianDateDisplay().plusYears(1).withChronology(GregorianChronology.getInstance());
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Decrease by 1 day
*/
private void decrementDay(){
DateTime dt = getDateAsGregorian().minusDays(1);
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Decrease by 1 month
*/
private void decrementMonth(){
DateTime dt = getCurrentEthiopianDateDisplay().minusMonths(1).withChronology(GregorianChronology.getInstance());
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Decrease by 1 year
*/
private void decrementYear(){
DateTime dt = getCurrentEthiopianDateDisplay().minusYears(1).withChronology(GregorianChronology.getInstance());
updateEthiopianDateDisplay(dt);
updateGregorianDateHelperDisplay();
}
/**
* Initial date display
*/
private void setAnswer() {
if (mPrompt.getAnswerValue() != null) {
// setup date object
DateTime dtISO = new DateTime(((Date) ((DateData) mPrompt.getAnswerValue()).getValue()).getTime());
// find out what the same instant is using the Ethiopic Chronology
DateTime dtEthiopic = dtISO.withChronology(chron_eth);
txtDay.setText(Integer.toString(dtEthiopic.getDayOfMonth()));
txtMonth.setText(monthsArray[dtEthiopic.getMonthOfYear()-1]);
ethiopianMonthArrayPointer = dtEthiopic.getMonthOfYear()-1;
txtYear.setText(Integer.toString(dtEthiopic.getYear()));
updateGregorianDateHelperDisplay();
} else {
// create date widget with current date
clearAnswer();
}
}
/**
* Get the current widget date in Gregorian chronology
* @return
*/
private DateTime getDateAsGregorian(){
DateTime dtGregorian = getCurrentEthiopianDateDisplay().withChronology(GregorianChronology.getInstance());
return dtGregorian;
}
/**
* Get the current widget date in Ethiopian chronology
* @return
*/
private DateTime getCurrentEthiopianDateDisplay(){
int ethioDay = Integer.parseInt(txtDay.getText().toString());
int ethioMonth = ethiopianMonthArrayPointer + 1;
int ethioYear = Integer.parseInt(txtYear.getText().toString());
return new DateTime(ethioYear, ethioMonth, ethioDay, 0, 0, 0, 0, chron_eth);
}
/**
* Update the widget date to display the amended date
* @param dtGreg
*/
private void updateEthiopianDateDisplay(DateTime dtGreg){
DateTime dtEthio = dtGreg.withChronology(chron_eth);
txtDay.setText(String.format("%02d",dtEthio.getDayOfMonth()));
txtMonth.setText(monthsArray[dtEthio.getMonthOfYear()-1]);
ethiopianMonthArrayPointer = dtEthio.getMonthOfYear()-1;
txtYear.setText(String.format("%04d",dtEthio.getYear()));
}
/**
* Update the widget helper date text (useful for those who don't know the Ethiopian calendar)
* @param dtGreg
*/
private void updateGregorianDateHelperDisplay(){
DateTime dtLMDGreg = getCurrentEthiopianDateDisplay().withChronology(GregorianChronology.getInstance());
DateTimeFormatter fmt = DateTimeFormat.forPattern("d MMMM yyyy");
String str = fmt.print(dtLMDGreg);
txtGregorian.setText("("+str+")");
}
/**
* Listens for button being pressed by touchscreen
* @author alex
*/
private class EDWTouchListener implements OnTouchListener{
private View mView;
private Handler mHandler;
public EDWTouchListener(View mV, Handler mH){
mView = mV;
mHandler = mH;
}
/*
* (non-Javadoc)
* @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean isReleased = event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL;
boolean isPressed = event.getAction() == MotionEvent.ACTION_DOWN;
if (isReleased) {
stopUpdating();
} else if (isPressed) {
startUpdating(v == mView,mHandler);
}
return false;
}
}
/**
* Listens for button being pressed by keypad/trackball
* @author alex
*/
private class EDWKeyListener implements OnKeyListener{
private View mView;
private Handler mHandler;
public EDWKeyListener(View mV, Handler mH){
mView = mV;
mHandler = mH;
}
/*
* (non-Javadoc)
* @see android.view.View.OnKeyListener#onKey(android.view.View, int, android.view.KeyEvent)
*/
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
boolean isKeyOfInterest = keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER;
boolean isReleased = event.getAction() == KeyEvent.ACTION_UP;
boolean isPressed = event.getAction() == KeyEvent.ACTION_DOWN
&& event.getAction() != KeyEvent.ACTION_MULTIPLE;
if (isKeyOfInterest && isReleased) {
stopUpdating();
} else if (isKeyOfInterest && isPressed) {
startUpdating(v == mView,mHandler);
}
return false;
}
}
}