Skip to content

Commit e7115df

Browse files
committed
#7 Implement Date format preference dialog and corresponding formatter
1 parent 62d4300 commit e7115df

File tree

8 files changed

+349
-63
lines changed

8 files changed

+349
-63
lines changed

app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatDialog.java

+99-4
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,40 @@
22

33
import android.content.Context;
44
import android.os.Bundle;
5+
import android.text.Editable;
6+
import android.text.TextWatcher;
7+
import android.view.KeyEvent;
58
import android.view.LayoutInflater;
69
import android.view.View;
10+
import android.widget.AdapterView;
711
import android.widget.ArrayAdapter;
12+
import android.widget.EditText;
813
import android.widget.LinearLayout;
914
import android.widget.Spinner;
15+
import android.widget.TextView;
1016

1117
import androidx.preference.PreferenceDialogFragmentCompat;
1218

1319
import org.andstatus.todoagenda.R;
20+
import org.andstatus.todoagenda.prefs.AllSettings;
21+
import org.andstatus.todoagenda.prefs.ApplicationPreferences;
22+
import org.andstatus.todoagenda.prefs.InstanceSettings;
1423

15-
public class DateFormatDialog extends PreferenceDialogFragmentCompat {
24+
import java.text.ParseException;
25+
import java.text.SimpleDateFormat;
26+
import java.util.Date;
27+
import java.util.Locale;
28+
29+
/**
30+
31+
*/
32+
public class DateFormatDialog extends PreferenceDialogFragmentCompat implements AdapterView.OnItemSelectedListener, View.OnKeyListener, TextWatcher {
1633
private final DateFormatPreference preference;
17-
private LinearLayout dialogView;
1834
private Spinner typeSpinner;
35+
private EditText customPatternText;
36+
private EditText sampleDateText;
37+
private TextView resultText;
38+
private DateFormatValue sampleDateFormatValue = DateFormatValue.of(DateFormatType.CUSTOM, "yyyy-MM-dd");
1939

2040
public DateFormatDialog(DateFormatPreference preference) {
2141
this.preference = preference;
@@ -27,18 +47,67 @@ public DateFormatDialog(DateFormatPreference preference) {
2747

2848
@Override
2949
protected View onCreateDialogView(Context context) {
30-
dialogView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.dateformat_preference, null);
50+
LinearLayout dialogView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.dateformat_preference, null);
3151

3252
typeSpinner = dialogView.findViewById(R.id.date_format_type);
3353
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
3454
context, android.R.layout.simple_spinner_item, DateFormatType.getSpinnerEntryList(context));
3555
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
3656
typeSpinner.setAdapter(adapter);
3757
typeSpinner.setSelection(preference.getValue().type.getSpinnerPosition());
58+
typeSpinner.setOnItemSelectedListener(this);
59+
60+
customPatternText = dialogView.findViewById(R.id.custom_pattern);
61+
customPatternText.setText(preference.getValue().getPattern());
62+
customPatternText.addTextChangedListener(this);
63+
64+
sampleDateText = dialogView.findViewById(R.id.sample_date);
65+
sampleDateText.setText(getSampleDateText());
66+
sampleDateText.addTextChangedListener(this);
67+
68+
resultText = dialogView.findViewById(R.id.result);
3869

3970
return dialogView;
4071
}
4172

73+
private CharSequence getSampleDateText() {
74+
return new DateFormatter(getContext(), sampleDateFormatValue, getSettings().clock().now())
75+
.formatMillis(getSettings().clock().now().getMillis());
76+
}
77+
78+
@Override
79+
public void onResume() {
80+
super.onResume();
81+
calcResult();
82+
}
83+
84+
// Two methods to listen for the Spinner changes
85+
@Override
86+
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
87+
if (getValue().hasPattern()) {
88+
customPatternText.setText(getValue().getPattern());
89+
}
90+
calcResult();
91+
}
92+
@Override
93+
public void onNothingSelected(AdapterView<?> parent) {
94+
calcResult();
95+
}
96+
97+
// Four methods to listen to the Custom pattern text changes
98+
@Override
99+
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
100+
@Override
101+
public void onTextChanged(CharSequence s, int start, int before, int count) { }
102+
@Override
103+
public void afterTextChanged(Editable s) {
104+
calcResult();
105+
}
106+
@Override
107+
public boolean onKey(View v, int keyCode, KeyEvent event) {
108+
return false;
109+
}
110+
42111
@Override
43112
public void onDialogClosed(boolean positiveResult) {
44113
if (positiveResult) {
@@ -51,6 +120,32 @@ public void onDialogClosed(boolean positiveResult) {
51120

52121
private DateFormatValue getValue() {
53122
int position = typeSpinner.getSelectedItemPosition();
54-
return position >= 0 ? DateFormatType.values()[position].defaultValue() : DateFormatType.UNKNOWN.defaultValue();
123+
return position >= 0
124+
? DateFormatValue.of(DateFormatType.values()[position], customPatternText.getText().toString())
125+
: DateFormatType.UNKNOWN.defaultValue();
126+
}
127+
128+
private void calcResult() {
129+
SimpleDateFormat sampleFormat = getSampleDateFormat();
130+
CharSequence result;
131+
try {
132+
Date sampleDate = sampleFormat.parse(sampleDateText.getText().toString());
133+
result = sampleDate == null
134+
? "null"
135+
: new DateFormatter(this.getContext(), getValue(), getSettings().clock().now())
136+
.formatMillis(sampleDate.getTime());
137+
} catch (ParseException e) {
138+
result = e.getLocalizedMessage();
139+
}
140+
resultText.setText(result);
141+
}
142+
143+
private SimpleDateFormat getSampleDateFormat() {
144+
return new SimpleDateFormat(sampleDateFormatValue.getPattern(), Locale.ENGLISH);
145+
}
146+
147+
private InstanceSettings getSettings() {
148+
int widgetId = ApplicationPreferences.getWidgetId(getActivity());
149+
return AllSettings.instanceFromId(getActivity(), widgetId);
55150
}
56151
}

app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatPreference.java

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import org.andstatus.todoagenda.prefs.ApplicationPreferences;
1212

13+
/**
14+
15+
*/
1316
public class DateFormatPreference extends DialogPreference {
1417
DateFormatValue defaultValue = DateFormatType.unknownValue();
1518
DateFormatValue value = DateFormatType.unknownValue();

app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatType.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,35 @@
1212
import java.util.List;
1313

1414
/** See https://github.com/andstatus/todoagenda/issues/7
15+
1516
* */
1617
public enum DateFormatType {
17-
HIDDEN("hidden", R.string.hidden),
18-
DEFAULT_DEVICE("device", R.string.device_default),
19-
DEFAULT_ABBREVIATED("abbrev", R.string.appearance_abbreviate_dates_title),
20-
NUMBER_OF_DAYS("days", R.string.date_format_number_of_days_to_event),
21-
CUSTOM("custom-01", R.string.custom),
22-
UNKNOWN("unknown", R.string.not_found);
18+
HIDDEN("hidden", R.string.hidden, ""),
19+
DEVICE_DEFAULT("deviceDefault", R.string.device_default, ""),
20+
DEFAULT_WEEKDAY("defaultWeekday", R.string.date_format_default_weekday, ""),
21+
DEFAULT_DAYS("defaultDays", R.string.date_format_default_days, ""),
22+
ABBREVIATED("abbrev", R.string.appearance_abbreviate_dates_title, ""),
23+
NUMBER_OF_DAYS("days", R.string.date_format_number_of_days_to_event, ""),
24+
DAY_IN_MONTH("dayInMonth", R.string.date_format_day_in_month, "dd"),
25+
MONTH_DAY("monthDay", R.string.date_format_month_day, "MM-dd"),
26+
WEEK_YEAR("weekInYear", R.string.date_format_week_in_year, "ww"),
27+
CUSTOM("custom-01", R.string.custom_pattern, ""),
28+
UNKNOWN("unknown", R.string.not_found, "");
2329

2430
public final String code;
2531
@StringRes
2632
public final int titleResourceId;
33+
public final String pattern;
2734

28-
public final static DateFormatType DEFAULT = DEFAULT_DEVICE;
35+
public final static DateFormatType DEFAULT = DEFAULT_WEEKDAY;
2936

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

33-
DateFormatType(String code, int titleResourceId) {
40+
DateFormatType(String code, int titleResourceId, String pattern) {
3441
this.code = code;
3542
this.titleResourceId = titleResourceId;
43+
this.pattern = pattern;
3644
}
3745

3846
@NonNull

app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatValue.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
import androidx.annotation.NonNull;
66

7+
import org.andstatus.todoagenda.util.StringUtil;
8+
9+
import static org.andstatus.todoagenda.prefs.dateformat.DateFormatType.CUSTOM;
10+
11+
/**
12+
13+
*/
714
public class DateFormatValue {
815
public final DateFormatType type;
916
public final String value;
@@ -25,12 +32,26 @@ public static DateFormatValue load(String storedValue, @NonNull DateFormatValue
2532
return DateFormatType.load(storedValue, defaultValue);
2633
}
2734

35+
public static DateFormatValue of(DateFormatType type, String value) {
36+
return type == CUSTOM && StringUtil.nonEmpty(value)
37+
? new DateFormatValue(type, value)
38+
: type.defaultValue();
39+
}
40+
2841
@NonNull
2942
public String save() {
3043
return type == DateFormatType.UNKNOWN ? "" : type.code + ":" + value;
3144
}
3245

46+
public boolean hasPattern() {
47+
return StringUtil.nonEmpty(getPattern());
48+
}
49+
50+
public String getPattern() {
51+
return StringUtil.isEmpty(value) ? type.pattern : value;
52+
}
53+
3354
public CharSequence getSummary(Context context) {
34-
return context.getText(type.titleResourceId);
55+
return context.getText(type.titleResourceId) + (type == CUSTOM ? ": \"" + value + "\"" : "");
3556
}
3657
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.andstatus.todoagenda.prefs.dateformat;
2+
3+
import android.content.Context;
4+
import android.text.format.DateUtils;
5+
6+
import org.andstatus.todoagenda.R;
7+
import org.joda.time.DateTime;
8+
import org.joda.time.Days;
9+
import org.joda.time.Instant;
10+
11+
import java.text.SimpleDateFormat;
12+
import java.util.Date;
13+
import java.util.Formatter;
14+
import java.util.Locale;
15+
16+
/**
17+
18+
*/
19+
public class DateFormatter {
20+
private final Context context;
21+
private final DateFormatValue dateFormatValue;
22+
private final DateTime now;
23+
Locale locale = Locale.getDefault();
24+
25+
public DateFormatter(Context context, DateFormatValue dateFormatValue, DateTime now) {
26+
this.context = context;
27+
this.dateFormatValue = dateFormatValue;
28+
this.now = now;
29+
}
30+
31+
public CharSequence formatMillis(long millis) {
32+
try {
33+
if(dateFormatValue.hasPattern()) {
34+
return formatDateCustom(millis, dateFormatValue.getPattern());
35+
}
36+
37+
switch (dateFormatValue.type) {
38+
case HIDDEN:
39+
return "";
40+
case DEVICE_DEFAULT:
41+
return formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE);
42+
case DEFAULT_WEEKDAY:
43+
return formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY);
44+
case ABBREVIATED:
45+
return formatDateTime(millis, DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
46+
DateUtils.FORMAT_SHOW_WEEKDAY);
47+
case DEFAULT_DAYS:
48+
return getDaysFromTodayString(context, getDaysFromToday(millis)) + ", " +
49+
formatDateTime(millis, DateUtils.FORMAT_SHOW_DATE);
50+
case NUMBER_OF_DAYS:
51+
return getDaysFromTodayString(context, getDaysFromToday(millis));
52+
default:
53+
return "(not implemented)";
54+
}
55+
} catch (Exception e) {
56+
return e.getLocalizedMessage();
57+
}
58+
}
59+
60+
private String formatDateTime(long millis, int flags) {
61+
return DateUtils.formatDateRange(context,
62+
new Formatter(new StringBuilder(50), locale),
63+
millis,
64+
millis,
65+
flags,
66+
now.getZone().getID())
67+
.toString();
68+
}
69+
70+
public static CharSequence getDaysFromTodayString(Context context, int daysFromToday) {
71+
switch (daysFromToday) {
72+
case -1:
73+
return context.getText(R.string.yesterday);
74+
case 0:
75+
return context.getText(R.string.today);
76+
case 1:
77+
return context.getText(R.string.tomorrow);
78+
default:
79+
return Math.abs(daysFromToday) > 9999 ? "..." : Integer.toString(daysFromToday);
80+
}
81+
}
82+
83+
public int getDaysFromToday(long millis) {
84+
return Days.daysBetween(now.withTimeAtStartOfDay(),
85+
Instant.ofEpochMilli(millis)).getDays();
86+
}
87+
88+
private String formatDateCustom(long millis, String pattern) {
89+
try {
90+
SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
91+
Date date = new Date(millis);
92+
return format.format(date);
93+
} catch (Exception e) {
94+
return e.getLocalizedMessage();
95+
}
96+
}
97+
98+
}

app/src/main/java/org/andstatus/todoagenda/widget/EventEntryLayout.java

+16-10
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import android.view.View;
55
import android.widget.RemoteViews;
66

7+
import androidx.annotation.LayoutRes;
8+
import androidx.annotation.StringRes;
9+
710
import org.andstatus.todoagenda.R;
811
import org.andstatus.todoagenda.prefs.InstanceSettings;
912
import org.andstatus.todoagenda.prefs.TextShadingPref;
1013
import org.andstatus.todoagenda.prefs.dateformat.DateFormatType;
11-
import org.andstatus.todoagenda.util.DateUtil;
14+
import org.andstatus.todoagenda.prefs.dateformat.DateFormatter;
1215
import org.andstatus.todoagenda.util.RemoteViewsUtil;
1316

14-
import androidx.annotation.LayoutRes;
15-
import androidx.annotation.StringRes;
16-
1717
import static org.andstatus.todoagenda.util.RemoteViewsUtil.setMultiline;
1818
import static org.andstatus.todoagenda.util.RemoteViewsUtil.setTextColorFromAttr;
1919
import static org.andstatus.todoagenda.util.RemoteViewsUtil.setTextSize;
@@ -49,24 +49,30 @@ protected String getTitleString(CalendarEntry event) {
4949

5050
@Override
5151
protected void setDaysToEvent(CalendarEntry entry, RemoteViews rv) {
52-
if (entry.getSettings().getEntryDateFormat().type == DateFormatType.NUMBER_OF_DAYS) {
52+
if (entry.getSettings().getEntryDateFormat().type == DateFormatType.HIDDEN) {
53+
rv.setViewVisibility(R.id.event_entry_days, View.GONE);
54+
rv.setViewVisibility(R.id.event_entry_days_right, View.GONE);
55+
} else {
56+
DateFormatter formatter = new DateFormatter(entry.getContext(), entry.getSettings().getEntryDateFormat(),
57+
entry.getSettings().clock().now());
58+
5359
int days = entry.getDaysFromToday();
54-
boolean daysAsText = days > -2 && days < 2;
60+
boolean daysAsText = entry.getSettings().getEntryDateFormat().type != DateFormatType.NUMBER_OF_DAYS ||
61+
(days > -2 && days < 2);
62+
5563
int viewToShow = daysAsText ? R.id.event_entry_days : R.id.event_entry_days_right;
5664
int viewToHide = daysAsText ? R.id.event_entry_days_right : R.id.event_entry_days;
5765
rv.setViewVisibility(viewToHide, View.GONE);
5866
rv.setViewVisibility(viewToShow, View.VISIBLE);
59-
rv.setTextViewText(viewToShow, DateUtil.getDaysFromTodayString(entry.getContext(), days));
67+
68+
rv.setTextViewText(viewToShow, formatter.formatMillis(entry.entryDate.getMillis()));
6069
InstanceSettings settings = entry.getSettings();
6170
setViewWidth(settings, rv, viewToShow, daysAsText
6271
? R.dimen.days_to_event_width
6372
: R.dimen.days_to_event_right_width);
6473
setTextSize(settings, rv, viewToShow, R.dimen.event_entry_details);
6574
setTextColorFromAttr(settings.getShadingContext(TextShadingPref.forDetails(entry)),
6675
rv, viewToShow, R.attr.dayHeaderTitle);
67-
} else {
68-
rv.setViewVisibility(R.id.event_entry_days, View.GONE);
69-
rv.setViewVisibility(R.id.event_entry_days_right, View.GONE);
7076
}
7177
}
7278

0 commit comments

Comments
 (0)