Skip to content

Commit

Permalink
#7 Implement Date format preference dialog and corresponding formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
yvolk committed Feb 12, 2020
1 parent 62d4300 commit e7115df
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,40 @@

import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.preference.PreferenceDialogFragmentCompat;

import org.andstatus.todoagenda.R;
import org.andstatus.todoagenda.prefs.AllSettings;
import org.andstatus.todoagenda.prefs.ApplicationPreferences;
import org.andstatus.todoagenda.prefs.InstanceSettings;

public class DateFormatDialog extends PreferenceDialogFragmentCompat {
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
* @author yvolk@yurivolkov.com
*/
public class DateFormatDialog extends PreferenceDialogFragmentCompat implements AdapterView.OnItemSelectedListener, View.OnKeyListener, TextWatcher {
private final DateFormatPreference preference;
private LinearLayout dialogView;
private Spinner typeSpinner;
private EditText customPatternText;
private EditText sampleDateText;
private TextView resultText;
private DateFormatValue sampleDateFormatValue = DateFormatValue.of(DateFormatType.CUSTOM, "yyyy-MM-dd");

public DateFormatDialog(DateFormatPreference preference) {
this.preference = preference;
Expand All @@ -27,18 +47,67 @@ public DateFormatDialog(DateFormatPreference preference) {

@Override
protected View onCreateDialogView(Context context) {
dialogView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.dateformat_preference, null);
LinearLayout dialogView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.dateformat_preference, null);

typeSpinner = dialogView.findViewById(R.id.date_format_type);
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
context, android.R.layout.simple_spinner_item, DateFormatType.getSpinnerEntryList(context));
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
typeSpinner.setAdapter(adapter);
typeSpinner.setSelection(preference.getValue().type.getSpinnerPosition());
typeSpinner.setOnItemSelectedListener(this);

customPatternText = dialogView.findViewById(R.id.custom_pattern);
customPatternText.setText(preference.getValue().getPattern());
customPatternText.addTextChangedListener(this);

sampleDateText = dialogView.findViewById(R.id.sample_date);
sampleDateText.setText(getSampleDateText());
sampleDateText.addTextChangedListener(this);

resultText = dialogView.findViewById(R.id.result);

return dialogView;
}

private CharSequence getSampleDateText() {
return new DateFormatter(getContext(), sampleDateFormatValue, getSettings().clock().now())
.formatMillis(getSettings().clock().now().getMillis());
}

@Override
public void onResume() {
super.onResume();
calcResult();
}

// Two methods to listen for the Spinner changes
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (getValue().hasPattern()) {
customPatternText.setText(getValue().getPattern());
}
calcResult();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
calcResult();
}

// Four methods to listen to the Custom pattern text changes
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override
public void afterTextChanged(Editable s) {
calcResult();
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
return false;
}

@Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
Expand All @@ -51,6 +120,32 @@ public void onDialogClosed(boolean positiveResult) {

private DateFormatValue getValue() {
int position = typeSpinner.getSelectedItemPosition();
return position >= 0 ? DateFormatType.values()[position].defaultValue() : DateFormatType.UNKNOWN.defaultValue();
return position >= 0
? DateFormatValue.of(DateFormatType.values()[position], customPatternText.getText().toString())
: DateFormatType.UNKNOWN.defaultValue();
}

private void calcResult() {
SimpleDateFormat sampleFormat = getSampleDateFormat();
CharSequence result;
try {
Date sampleDate = sampleFormat.parse(sampleDateText.getText().toString());
result = sampleDate == null
? "null"
: new DateFormatter(this.getContext(), getValue(), getSettings().clock().now())
.formatMillis(sampleDate.getTime());
} catch (ParseException e) {
result = e.getLocalizedMessage();
}
resultText.setText(result);
}

private SimpleDateFormat getSampleDateFormat() {
return new SimpleDateFormat(sampleDateFormatValue.getPattern(), Locale.ENGLISH);
}

private InstanceSettings getSettings() {
int widgetId = ApplicationPreferences.getWidgetId(getActivity());
return AllSettings.instanceFromId(getActivity(), widgetId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

import org.andstatus.todoagenda.prefs.ApplicationPreferences;

/**
* @author yvolk@yurivolkov.com
*/
public class DateFormatPreference extends DialogPreference {
DateFormatValue defaultValue = DateFormatType.unknownValue();
DateFormatValue value = DateFormatType.unknownValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,35 @@
import java.util.List;

/** See https://github.com/andstatus/todoagenda/issues/7
* @author yvolk@yurivolkov.com
* */
public enum DateFormatType {
HIDDEN("hidden", R.string.hidden),
DEFAULT_DEVICE("device", R.string.device_default),
DEFAULT_ABBREVIATED("abbrev", R.string.appearance_abbreviate_dates_title),
NUMBER_OF_DAYS("days", R.string.date_format_number_of_days_to_event),
CUSTOM("custom-01", R.string.custom),
UNKNOWN("unknown", R.string.not_found);
HIDDEN("hidden", R.string.hidden, ""),
DEVICE_DEFAULT("deviceDefault", R.string.device_default, ""),
DEFAULT_WEEKDAY("defaultWeekday", R.string.date_format_default_weekday, ""),
DEFAULT_DAYS("defaultDays", R.string.date_format_default_days, ""),
ABBREVIATED("abbrev", R.string.appearance_abbreviate_dates_title, ""),
NUMBER_OF_DAYS("days", R.string.date_format_number_of_days_to_event, ""),
DAY_IN_MONTH("dayInMonth", R.string.date_format_day_in_month, "dd"),
MONTH_DAY("monthDay", R.string.date_format_month_day, "MM-dd"),
WEEK_YEAR("weekInYear", R.string.date_format_week_in_year, "ww"),
CUSTOM("custom-01", R.string.custom_pattern, ""),
UNKNOWN("unknown", R.string.not_found, "");

public final String code;
@StringRes
public final int titleResourceId;
public final String pattern;

public final static DateFormatType DEFAULT = DEFAULT_DEVICE;
public final static DateFormatType DEFAULT = DEFAULT_WEEKDAY;

private final LazyVal<DateFormatValue> defaultValue = LazyVal.of( () ->
new DateFormatValue(DateFormatType.this, ""));

DateFormatType(String code, int titleResourceId) {
DateFormatType(String code, int titleResourceId, String pattern) {
this.code = code;
this.titleResourceId = titleResourceId;
this.pattern = pattern;
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

import androidx.annotation.NonNull;

import org.andstatus.todoagenda.util.StringUtil;

import static org.andstatus.todoagenda.prefs.dateformat.DateFormatType.CUSTOM;

/**
* @author yvolk@yurivolkov.com
*/
public class DateFormatValue {
public final DateFormatType type;
public final String value;
Expand All @@ -25,12 +32,26 @@ public static DateFormatValue load(String storedValue, @NonNull DateFormatValue
return DateFormatType.load(storedValue, defaultValue);
}

public static DateFormatValue of(DateFormatType type, String value) {
return type == CUSTOM && StringUtil.nonEmpty(value)
? new DateFormatValue(type, value)
: type.defaultValue();
}

@NonNull
public String save() {
return type == DateFormatType.UNKNOWN ? "" : type.code + ":" + value;
}

public boolean hasPattern() {
return StringUtil.nonEmpty(getPattern());
}

public String getPattern() {
return StringUtil.isEmpty(value) ? type.pattern : value;
}

public CharSequence getSummary(Context context) {
return context.getText(type.titleResourceId);
return context.getText(type.titleResourceId) + (type == CUSTOM ? ": \"" + value + "\"" : "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.andstatus.todoagenda.prefs.dateformat;

import android.content.Context;
import android.text.format.DateUtils;

import org.andstatus.todoagenda.R;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Instant;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.Locale;

/**
* @author yvolk@yurivolkov.com
*/
public class DateFormatter {
private final Context context;
private final DateFormatValue dateFormatValue;
private final DateTime now;
Locale locale = Locale.getDefault();

public DateFormatter(Context context, DateFormatValue dateFormatValue, DateTime now) {
this.context = context;
this.dateFormatValue = dateFormatValue;
this.now = now;
}

public CharSequence formatMillis(long millis) {
try {
if(dateFormatValue.hasPattern()) {
return formatDateCustom(millis, dateFormatValue.getPattern());
}

switch (dateFormatValue.type) {
case HIDDEN:
return "";
case DEVICE_DEFAULT:
return formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE);
case DEFAULT_WEEKDAY:
return formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY);
case ABBREVIATED:
return formatDateTime(millis, DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_WEEKDAY);
case DEFAULT_DAYS:
return getDaysFromTodayString(context, getDaysFromToday(millis)) + ", " +
formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE);
case NUMBER_OF_DAYS:
return getDaysFromTodayString(context, getDaysFromToday(millis));
default:
return "(not implemented)";
}
} catch (Exception e) {
return e.getLocalizedMessage();
}
}

private String formatDateTime(long millis, int flags) {
return DateUtils.formatDateRange(context,
new Formatter(new StringBuilder(50), locale),
millis,
millis,
flags,
now.getZone().getID())
.toString();
}

public static CharSequence getDaysFromTodayString(Context context, int daysFromToday) {
switch (daysFromToday) {
case -1:
return context.getText(R.string.yesterday);
case 0:
return context.getText(R.string.today);
case 1:
return context.getText(R.string.tomorrow);
default:
return Math.abs(daysFromToday) > 9999 ? "..." : Integer.toString(daysFromToday);
}
}

public int getDaysFromToday(long millis) {
return Days.daysBetween(now.withTimeAtStartOfDay(),
Instant.ofEpochMilli(millis)).getDays();
}

private String formatDateCustom(long millis, String pattern) {
try {
SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
Date date = new Date(millis);
return format.format(date);
} catch (Exception e) {
return e.getLocalizedMessage();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import android.view.View;
import android.widget.RemoteViews;

import androidx.annotation.LayoutRes;
import androidx.annotation.StringRes;

import org.andstatus.todoagenda.R;
import org.andstatus.todoagenda.prefs.InstanceSettings;
import org.andstatus.todoagenda.prefs.TextShadingPref;
import org.andstatus.todoagenda.prefs.dateformat.DateFormatType;
import org.andstatus.todoagenda.util.DateUtil;
import org.andstatus.todoagenda.prefs.dateformat.DateFormatter;
import org.andstatus.todoagenda.util.RemoteViewsUtil;

import androidx.annotation.LayoutRes;
import androidx.annotation.StringRes;

import static org.andstatus.todoagenda.util.RemoteViewsUtil.setMultiline;
import static org.andstatus.todoagenda.util.RemoteViewsUtil.setTextColorFromAttr;
import static org.andstatus.todoagenda.util.RemoteViewsUtil.setTextSize;
Expand Down Expand Up @@ -49,24 +49,30 @@ protected String getTitleString(CalendarEntry event) {

@Override
protected void setDaysToEvent(CalendarEntry entry, RemoteViews rv) {
if (entry.getSettings().getEntryDateFormat().type == DateFormatType.NUMBER_OF_DAYS) {
if (entry.getSettings().getEntryDateFormat().type == DateFormatType.HIDDEN) {
rv.setViewVisibility(R.id.event_entry_days, View.GONE);
rv.setViewVisibility(R.id.event_entry_days_right, View.GONE);
} else {
DateFormatter formatter = new DateFormatter(entry.getContext(), entry.getSettings().getEntryDateFormat(),
entry.getSettings().clock().now());

int days = entry.getDaysFromToday();
boolean daysAsText = days > -2 && days < 2;
boolean daysAsText = entry.getSettings().getEntryDateFormat().type != DateFormatType.NUMBER_OF_DAYS ||
(days > -2 && days < 2);

int viewToShow = daysAsText ? R.id.event_entry_days : R.id.event_entry_days_right;
int viewToHide = daysAsText ? R.id.event_entry_days_right : R.id.event_entry_days;
rv.setViewVisibility(viewToHide, View.GONE);
rv.setViewVisibility(viewToShow, View.VISIBLE);
rv.setTextViewText(viewToShow, DateUtil.getDaysFromTodayString(entry.getContext(), days));

rv.setTextViewText(viewToShow, formatter.formatMillis(entry.entryDate.getMillis()));
InstanceSettings settings = entry.getSettings();
setViewWidth(settings, rv, viewToShow, daysAsText
? R.dimen.days_to_event_width
: R.dimen.days_to_event_right_width);
setTextSize(settings, rv, viewToShow, R.dimen.event_entry_details);
setTextColorFromAttr(settings.getShadingContext(TextShadingPref.forDetails(entry)),
rv, viewToShow, R.attr.dayHeaderTitle);
} else {
rv.setViewVisibility(R.id.event_entry_days, View.GONE);
rv.setViewVisibility(R.id.event_entry_days_right, View.GONE);
}
}

Expand Down
Loading

0 comments on commit e7115df

Please sign in to comment.