From e7115df32c19eaa9291686a3c50900fbd64eaa3e Mon Sep 17 00:00:00 2001 From: Yuri Volkov Date: Wed, 12 Feb 2020 22:43:02 +0300 Subject: [PATCH] #7 Implement Date format preference dialog and corresponding formatter --- .../prefs/dateformat/DateFormatDialog.java | 103 +++++++++++++++++- .../dateformat/DateFormatPreference.java | 3 + .../prefs/dateformat/DateFormatType.java | 24 ++-- .../prefs/dateformat/DateFormatValue.java | 23 +++- .../prefs/dateformat/DateFormatter.java | 98 +++++++++++++++++ .../todoagenda/widget/EventEntryLayout.java | 26 +++-- .../main/res/layout/dateformat_preference.xml | 51 ++++++++- app/src/main/res/values/strings.xml | 84 +++++++------- 8 files changed, 349 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatter.java diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatDialog.java b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatDialog.java index 6c80d8b3..fbe10a53 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatDialog.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatDialog.java @@ -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; @@ -27,7 +47,7 @@ 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 adapter = new ArrayAdapter<>( @@ -35,10 +55,59 @@ protected View onCreateDialogView(Context 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) { @@ -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); } } diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatPreference.java b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatPreference.java index b0ef8b2c..d42d8ac3 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatPreference.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatPreference.java @@ -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(); diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatType.java b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatType.java index e53ba163..38e9a4ca 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatType.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatType.java @@ -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 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 diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatValue.java b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatValue.java index e92b4922..939aa306 100644 --- a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatValue.java +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatValue.java @@ -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; @@ -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 + "\"" : ""); } } diff --git a/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatter.java b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatter.java new file mode 100644 index 00000000..a648b5d7 --- /dev/null +++ b/app/src/main/java/org/andstatus/todoagenda/prefs/dateformat/DateFormatter.java @@ -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(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/andstatus/todoagenda/widget/EventEntryLayout.java b/app/src/main/java/org/andstatus/todoagenda/widget/EventEntryLayout.java index b9e931c3..a4d3a09b 100644 --- a/app/src/main/java/org/andstatus/todoagenda/widget/EventEntryLayout.java +++ b/app/src/main/java/org/andstatus/todoagenda/widget/EventEntryLayout.java @@ -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; @@ -49,14 +49,23 @@ 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 @@ -64,9 +73,6 @@ protected void setDaysToEvent(CalendarEntry entry, RemoteViews rv) { 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); } } diff --git a/app/src/main/res/layout/dateformat_preference.xml b/app/src/main/res/layout/dateformat_preference.xml index e3e88e06..9e8390ac 100644 --- a/app/src/main/res/layout/dateformat_preference.xml +++ b/app/src/main/res/layout/dateformat_preference.xml @@ -1,12 +1,59 @@ + android:orientation="vertical" + android:padding="10dp"> + + + + + + + + android:inputType="date" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e22d17a9..b4f3f4d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,37 +66,7 @@ cloning widgets on the same or different devices. Android 7+ supported. Supports Android tablets. - - Calendars and Task Lists - Select the calendars and task lists to display - OpenTasks (by dmfs GmbH) - Samsung Calendar - - - Device default - Size, date and time format, etc. - Other settings - Appearance - Adjust the appearance of the widget - Text size - Adjust the text size - Very small - Small - Medium - Large - Very large - Show time in am/pm or 24 hours format - Date format - Automatic - 17:00 - 5:00 pm - Unlocked, current zone: %s - Locked at %s - Lock time zone - Abbreviate dates - Use three-letter format for dates in Widget and Day headers etc. - - + Place, show and hide widget elements Layout Widget header layout @@ -124,12 +94,24 @@ Show event icon Show Calendar color bar / Task icon for event Entry date format - Number of days to event - Custom Multiline details Spread event time and location over multiple lines - + + Simple options + Default with Weekday + Default with Number of days to event + Abbreviate dates with Weekday + Use three-letter format for dates + Number of days to event + Day in month + Month-Day + Week in year + Custom pattern + Sample date (in yyyy-MM-dd format) + Result, formatted date + + Colors and opacity of texts and backgrounds Colors Background color @@ -151,7 +133,7 @@ Dark Black - + Event details Configure what details to show for an event End time @@ -166,7 +148,7 @@ Show indicator for recurring events Recurring events - + Event filters Which current, future and past events to show All events @@ -213,7 +195,33 @@ Debug filtering Ignore all filters (turn them off) - + + Calendars and Task Lists + Select the calendars and task lists to display + OpenTasks (by dmfs GmbH) + Samsung Calendar + + + Device default + Size, date and time format, etc. + Other settings + Appearance + Adjust the appearance of the widget + Text size + Adjust the text size + Very small + Small + Medium + Large + Very large + Show time in am/pm or 24 hours format + Date format + Automatic + 17:00 + 5:00 pm + Unlocked, current zone: %s + Locked at %s + Lock time zone Lock list of events (Snapshot mode) Live data from real sources Snapshot as seen at the time, when it was taken: %s @@ -221,7 +229,7 @@ Auto refresh period, minutes Automatically refresh the widget every %d minute(s) - + Feedback and Backup Get in touch with us, backup and restore settings Provide feedback on the project page