Skip to content

Commit

Permalink
Add abstractions for non-Gregorian calendars such as Ethiopian (getod…
Browse files Browse the repository at this point in the history
…k#1505)

Calendars other than the Gregorian calendar can now be added more easily. These are introduced as appearances on the existing date types meaning that the underlying date is always Gregorian but the user sees pickers that follow the rules of the desired calendar.

The Ethiopian calendar is introduced through the ethiopian appearance. The implementation uses the Ethiopic joda Chronology. This appearance can be combined with others such as month-year or year.
  • Loading branch information
grzesiek2010 authored and lognaturel committed Oct 19, 2017
1 parent d73d536 commit 3cae266
Show file tree
Hide file tree
Showing 23 changed files with 1,288 additions and 263 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.javarosa.form.api.FormEntryCaption;
import org.javarosa.form.api.FormEntryController;
import org.javarosa.form.api.FormEntryPrompt;
import org.joda.time.LocalDateTime;
import org.odk.collect.android.R;
import org.odk.collect.android.adapters.IconMenuListAdapter;
import org.odk.collect.android.adapters.model.IconMenuItem;
Expand All @@ -83,6 +84,7 @@
import org.odk.collect.android.exception.GDriveConnectionException;
import org.odk.collect.android.exception.JavaRosaException;
import org.odk.collect.android.external.ExternalDataManager;
import org.odk.collect.android.fragments.dialogs.CustomDatePickerDialog;
import org.odk.collect.android.fragments.dialogs.NumberPickerDialog;
import org.odk.collect.android.listeners.AdvanceToNextListener;
import org.odk.collect.android.listeners.FormLoaderListener;
Expand Down Expand Up @@ -141,7 +143,8 @@
*/
public class FormEntryActivity extends AppCompatActivity implements AnimationListener,
FormLoaderListener, FormSavedListener, AdvanceToNextListener,
OnGestureListener, SavePointListener, NumberPickerDialog.NumberPickerListener {
OnGestureListener, SavePointListener, NumberPickerDialog.NumberPickerListener,
CustomDatePickerDialog.CustomDatePickerDialogListener {

// save with every swipe forward or back. Timings indicate this takes .25
// seconds.
Expand Down Expand Up @@ -3002,6 +3005,14 @@ public void onNumberPickerValueSelected(int widgetId, int value) {
}
}

@Override
public void onDateChanged(LocalDateTime date) {
ODKView odkView = getCurrentViewIfODKView();
if (odkView != null) {
odkView.setBinaryData(date);
}
}

/**
* getter for currentView variable. This method should always be used
* to access currentView as an ODKView object to avoid inconsistency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ public void refreshView() {
if (!fp.isReadOnly() || (label != null && label.length() > 0)) {
// show the question if it is an editable field.
// or if it is read-only and the label is not blank.
String answerDisplay = FormEntryPromptUtils.getAnswerText(fp);
String answerDisplay = FormEntryPromptUtils.getAnswerText(fp, this);
formList.add(
new HierarchyElement(fp.getLongText(), answerDisplay, null,
Color.WHITE, QUESTION, fp.getIndex()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* Copyright 2017 Nafundi
*
* 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 org.odk.collect.android.fragments.dialogs;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.TextView;

import org.javarosa.core.model.FormIndex;
import org.joda.time.LocalDateTime;
import org.joda.time.chrono.GregorianChronology;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.logic.DatePickerDetails;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.utilities.DateTimeUtils;

/**
* @author Grzegorz Orczykowski ([email protected])
*/
public abstract class CustomDatePickerDialog extends DialogFragment {
public static final String DATE_PICKER_DIALOG = "datePickerDialog";

private static final String FORM_INDEX = "formIndex";
private static final String DATE = "date";
private static final String DATE_PICKER_DETAILS = "datePickerDetails";

private NumberPicker dayPicker;
private NumberPicker monthPicker;
private NumberPicker yearPicker;

private LocalDateTime date;

private TextView gregorianDateText;

private FormIndex formIndex;

private DatePickerDetails datePickerDetails;

private CustomDatePickerDialogListener listener;

public interface CustomDatePickerDialogListener {
void onDateChanged(LocalDateTime date);
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof CustomDatePickerDialogListener) {
listener = (CustomDatePickerDialogListener) context;
}
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Bundle savedInstanceStateToRead = savedInstanceState;
if (savedInstanceStateToRead == null) {
savedInstanceStateToRead = getArguments();
}

formIndex = (FormIndex) savedInstanceStateToRead.getSerializable(FORM_INDEX);
date = (LocalDateTime) savedInstanceStateToRead.getSerializable(DATE);
datePickerDetails = (DatePickerDetails) savedInstanceStateToRead.getSerializable(DATE_PICKER_DETAILS);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.select_date)
.setView(R.layout.custom_date_picker_dialog)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
FormController formController = Collect.getInstance().getFormController();
if (formController != null) {
formController.setIndexWaitingForData(formIndex);
}
listener.onDateChanged(getDateAsGregorian(getOriginalDate()));
dismiss();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
}
})
.create();
}

@Override
public void onResume() {
super.onResume();
gregorianDateText = (TextView) getDialog().findViewById(R.id.date_gregorian);
setUpPickers();
}

@Override
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable(FORM_INDEX, formIndex);
outState.putSerializable(DATE, getDateAsGregorian(getOriginalDate()));
outState.putSerializable(DATE_PICKER_DETAILS, datePickerDetails);

super.onSaveInstanceState(outState);
}

private void setUpPickers() {
dayPicker = (NumberPicker) getDialog().findViewById(R.id.day_picker);
dayPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateGregorianDateLabel();
}
});
monthPicker = (NumberPicker) getDialog().findViewById(R.id.month_picker);
monthPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateDays();
updateGregorianDateLabel();
}
});
yearPicker = (NumberPicker) getDialog().findViewById(R.id.year_picker);
yearPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateDays();
updateGregorianDateLabel();
}
});

hidePickersIfNeeded();
}

private void hidePickersIfNeeded() {
if (datePickerDetails.isMonthYearMode()) {
dayPicker.setVisibility(View.GONE);
dayPicker.setValue(1);
} else if (datePickerDetails.isYearMode()) {
dayPicker.setVisibility(View.GONE);
monthPicker.setVisibility(View.GONE);
dayPicker.setValue(1);
yearPicker.setValue(1);
}
}

private LocalDateTime getDateAsGregorian(LocalDateTime date) {
return DateTimeUtils.skipDaylightSavingGapIfExists(date)
.toDateTime()
.withChronology(GregorianChronology.getInstance())
.toLocalDateTime();
}

protected static Bundle getArgs(FormIndex formIndex, LocalDateTime date, DatePickerDetails datePickerDetails) {
Bundle args = new Bundle();
args.putSerializable(FORM_INDEX, formIndex);
args.putSerializable(DATE, date);
args.putSerializable(DATE_PICKER_DETAILS, datePickerDetails);

return args;
}

protected void updateGregorianDateLabel() {
String label = DateTimeUtils.getDateTimeLabel(getDateAsGregorian(getOriginalDate()).toDate(), datePickerDetails, false, getContext());
gregorianDateText.setText(label);
}

protected void setUpDayPicker(LocalDateTime ethiopianDate) {
dayPicker.setMinValue(1);
dayPicker.setMaxValue(ethiopianDate.dayOfMonth().getMaximumValue());
if (datePickerDetails.isSpinnerMode()) {
dayPicker.setValue(ethiopianDate.getDayOfMonth());
}
}

protected void setUpMonthPicker(LocalDateTime ethiopianDate, String[] monthsArray) {
monthPicker.setMaxValue(monthsArray.length - 1);
monthPicker.setDisplayedValues(monthsArray);
if (!datePickerDetails.isYearMode()) {
monthPicker.setValue(ethiopianDate.getMonthOfYear() - 1);
}
}

protected void setUpYearPicker(LocalDateTime ethiopianDate, int minSupportedYear, int maxSupportedYear) {
yearPicker.setMinValue(minSupportedYear);
yearPicker.setMaxValue(maxSupportedYear);
yearPicker.setValue(ethiopianDate.getYear());
}

public int getDay() {
return dayPicker.getValue();
}

public String getMonth() {
return monthPicker.getDisplayedValues()[monthPicker.getValue()];
}

public int getYear() {
return yearPicker.getValue();
}

public LocalDateTime getDate() {
return date;
}

protected abstract void updateDays();

protected abstract LocalDateTime getOriginalDate();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2017 Nafundi
*
* 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 org.odk.collect.android.fragments.dialogs;

import org.javarosa.core.model.FormIndex;
import org.joda.time.LocalDateTime;
import org.joda.time.chrono.EthiopicChronology;
import org.odk.collect.android.R;
import org.odk.collect.android.logic.DatePickerDetails;
import org.odk.collect.android.utilities.DateTimeUtils;

import java.util.Arrays;

/**
* @author Grzegorz Orczykowski ([email protected])
* @author Aurelio Di Pasquale ([email protected])
*/
public class EthiopianDatePickerDialog extends CustomDatePickerDialog {
private static final int MIN_SUPPORTED_YEAR = 1893; //1900 in Gregorian calendar
private static final int MAX_SUPPORTED_YEAR = 2093; //2100 in Gregorian calendar

private String[] monthsArray;

public static EthiopianDatePickerDialog newInstance(FormIndex formIndex, LocalDateTime date, DatePickerDetails datePickerDetails) {
EthiopianDatePickerDialog dialog = new EthiopianDatePickerDialog();
dialog.setArguments(getArgs(formIndex, date, datePickerDetails));

return dialog;
}

@Override
public void onResume() {
super.onResume();
monthsArray = getResources().getStringArray(R.array.ethiopian_months);
setUpValues();
}

@Override
protected void updateDays() {
setUpDayPicker(getCurrentEthiopianDate());
}

@Override
protected LocalDateTime getOriginalDate() {
return getCurrentEthiopianDate();
}

private void setUpDatePicker() {
LocalDateTime ethiopianDate = DateTimeUtils
.skipDaylightSavingGapIfExists(getDate())
.toDateTime()
.withChronology(EthiopicChronology.getInstance())
.toLocalDateTime();
setUpDayPicker(ethiopianDate);
setUpMonthPicker(ethiopianDate, monthsArray);
setUpYearPicker(ethiopianDate, MIN_SUPPORTED_YEAR, MAX_SUPPORTED_YEAR);
}

private void setUpValues() {
setUpDatePicker();
updateGregorianDateLabel();
}

private LocalDateTime getCurrentEthiopianDate() {
int ethiopianDay = getDay();
int ethiopianMonth = Arrays.asList(monthsArray).indexOf(getMonth());
int ethiopianYear = getYear();

LocalDateTime ethiopianDate = new LocalDateTime(ethiopianYear, ethiopianMonth + 1, 1, 0, 0, 0, 0, EthiopicChronology.getInstance());
if (ethiopianDay > ethiopianDate.dayOfMonth().getMaximumValue()) {
ethiopianDay = ethiopianDate.dayOfMonth().getMaximumValue();
}

return new LocalDateTime(ethiopianYear, ethiopianMonth + 1, ethiopianDay, 0, 0, 0, 0, EthiopicChronology.getInstance());
}
}
Loading

0 comments on commit 3cae266

Please sign in to comment.