From 383485abecba071b5cb7179efd04d2fbc20cb287 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 6 Jul 2021 07:40:25 +0900 Subject: [PATCH] Handle invalid custom date time format. Fixes austinhuang0131/barinsta#1499 --- .../dialogs/TimeSettingsDialog.java | 113 ++++++++++-------- .../awais/instagrabber/utils/Constants.kt | 1 + .../awais/instagrabber/utils/DateUtils.kt | 12 ++ .../instagrabber/utils/SettingsHelper.kt | 4 +- .../awais/instagrabber/utils/TextUtils.kt | 11 +- .../main/res/layout/dialog_time_settings.xml | 36 +++--- app/src/main/res/values/strings.xml | 1 + 7 files changed, 104 insertions(+), 74 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java b/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java index 7ad34fe1..c7238bb1 100755 --- a/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java +++ b/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java @@ -16,18 +16,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; -import java.time.format.DateTimeFormatter; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import awais.instagrabber.R; import awais.instagrabber.databinding.DialogTimeSettingsBinding; +import awais.instagrabber.utils.DateUtils; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.TextUtils; public final class TimeSettingsDialog extends DialogFragment implements AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener, View.OnClickListener, TextWatcher { - private DialogTimeSettingsBinding timeSettingsBinding; + private DialogTimeSettingsBinding binding; private final LocalDateTime magicDate; private DateTimeFormatter currentFormat; private String selectedFormat; @@ -55,57 +57,67 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { - timeSettingsBinding = DialogTimeSettingsBinding.inflate(inflater, container, false); + binding = DialogTimeSettingsBinding.inflate(inflater, container, false); - timeSettingsBinding.cbCustomFormat.setOnCheckedChangeListener(this); - timeSettingsBinding.cbCustomFormat.setChecked(customDateTimeFormatEnabled); - timeSettingsBinding.cbSwapTimeDate.setChecked(swapDateTimeEnabled); - timeSettingsBinding.etCustomFormat.setText(customDateTimeFormat); + binding.cbCustomFormat.setOnCheckedChangeListener(this); + binding.cbCustomFormat.setChecked(customDateTimeFormatEnabled); + binding.cbSwapTimeDate.setChecked(swapDateTimeEnabled); + binding.customFormatEditText.setText(customDateTimeFormat); final String[] dateTimeFormat = dateTimeSelection.split(";"); // output = time;separator;date - timeSettingsBinding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0])); - timeSettingsBinding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1])); - timeSettingsBinding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2])); + binding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0])); + binding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1])); + binding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2])); - timeSettingsBinding.cbSwapTimeDate.setOnCheckedChangeListener(this); + binding.cbSwapTimeDate.setOnCheckedChangeListener(this); refreshTimeFormat(); - timeSettingsBinding.spTimeFormat.setOnItemSelectedListener(this); - timeSettingsBinding.spDateFormat.setOnItemSelectedListener(this); - timeSettingsBinding.spSeparator.setOnItemSelectedListener(this); + binding.spTimeFormat.setOnItemSelectedListener(this); + binding.spDateFormat.setOnItemSelectedListener(this); + binding.spSeparator.setOnItemSelectedListener(this); - timeSettingsBinding.etCustomFormat.addTextChangedListener(this); - timeSettingsBinding.btnConfirm.setOnClickListener(this); - timeSettingsBinding.btnInfo.setOnClickListener(this); + binding.customFormatEditText.addTextChangedListener(this); + binding.btnConfirm.setOnClickListener(this); + binding.customFormatField.setEndIconOnClickListener(this); - return timeSettingsBinding.getRoot(); + return binding.getRoot(); } private void refreshTimeFormat() { - if (timeSettingsBinding.cbCustomFormat.isChecked()) - selectedFormat = timeSettingsBinding.etCustomFormat.getText().toString(); - else { - final String sepStr = String.valueOf(timeSettingsBinding.spSeparator.getSelectedItem()); - final String timeStr = String.valueOf(timeSettingsBinding.spTimeFormat.getSelectedItem()); - final String dateStr = String.valueOf(timeSettingsBinding.spDateFormat.getSelectedItem()); + final boolean isCustom = binding.cbCustomFormat.isChecked(); + if (isCustom) { + final Editable text = binding.customFormatEditText.getText(); + if (text != null) { + selectedFormat = text.toString(); + } + } else { + final String sepStr = String.valueOf(binding.spSeparator.getSelectedItem()); + final String timeStr = String.valueOf(binding.spTimeFormat.getSelectedItem()); + final String dateStr = String.valueOf(binding.spDateFormat.getSelectedItem()); - final boolean isSwapTime = timeSettingsBinding.cbSwapTimeDate.isChecked(); - final boolean isBlankSeparator = timeSettingsBinding.spSeparator.getSelectedItemPosition() <= 0; + final boolean isSwapTime = binding.cbSwapTimeDate.isChecked(); + final boolean isBlankSeparator = binding.spSeparator.getSelectedItemPosition() <= 0; selectedFormat = (isSwapTime ? dateStr : timeStr) + (isBlankSeparator ? " " : " '" + sepStr + "' ") + (isSwapTime ? timeStr : dateStr); } - timeSettingsBinding.btnConfirm.setEnabled(true); + binding.btnConfirm.setEnabled(true); try { currentFormat = DateTimeFormatter.ofPattern(selectedFormat, LocaleUtils.getCurrentLocale()); - timeSettingsBinding.timePreview.setText(magicDate.format(currentFormat)); - } - catch (Exception e) { - timeSettingsBinding.btnConfirm.setEnabled(false); - timeSettingsBinding.timePreview.setText(null); + if (isCustom) { + final boolean valid = !TextUtils.isEmpty(selectedFormat) && DateUtils.checkFormatterValid(currentFormat); + binding.customFormatField.setError(valid ? null :getString(R.string.invalid_format)); + if (!valid) { + binding.btnConfirm.setEnabled(false); + } + } + binding.timePreview.setText(magicDate.format(currentFormat)); + } catch (Exception e) { + binding.btnConfirm.setEnabled(false); + binding.timePreview.setText(null); } } @@ -116,16 +128,14 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { - if (buttonView == timeSettingsBinding.cbCustomFormat) { - final View parent = (View) timeSettingsBinding.etCustomFormat.getParent(); - parent.setVisibility(isChecked ? View.VISIBLE : View.GONE); - timeSettingsBinding.etCustomFormat.setEnabled(isChecked); - timeSettingsBinding.btnInfo.setEnabled(isChecked); - - timeSettingsBinding.spTimeFormat.setEnabled(!isChecked); - timeSettingsBinding.spDateFormat.setEnabled(!isChecked); - timeSettingsBinding.spSeparator.setEnabled(!isChecked); - timeSettingsBinding.cbSwapTimeDate.setEnabled(!isChecked); + if (buttonView == binding.cbCustomFormat) { + binding.customFormatField.setVisibility(isChecked ? View.VISIBLE : View.GONE); + binding.customFormatField.setEnabled(isChecked); + + binding.spTimeFormat.setEnabled(!isChecked); + binding.spDateFormat.setEnabled(!isChecked); + binding.spSeparator.setEnabled(!isChecked); + binding.cbSwapTimeDate.setEnabled(!isChecked); } refreshTimeFormat(); } @@ -137,20 +147,21 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public void onClick(final View v) { - if (v == timeSettingsBinding.btnConfirm) { + if (v == binding.btnConfirm) { if (onConfirmListener != null) { onConfirmListener.onConfirm( - timeSettingsBinding.cbCustomFormat.isChecked(), - timeSettingsBinding.spTimeFormat.getSelectedItemPosition(), - timeSettingsBinding.spSeparator.getSelectedItemPosition(), - timeSettingsBinding.spDateFormat.getSelectedItemPosition(), + binding.cbCustomFormat.isChecked(), + binding.spTimeFormat.getSelectedItemPosition(), + binding.spSeparator.getSelectedItemPosition(), + binding.spDateFormat.getSelectedItemPosition(), selectedFormat, - timeSettingsBinding.cbSwapTimeDate.isChecked()); + binding.cbSwapTimeDate.isChecked()); } dismiss(); - } else if (v == timeSettingsBinding.btnInfo) { - timeSettingsBinding.customPanel.setVisibility(timeSettingsBinding.customPanel - .getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + } else if (v == binding.customFormatField.findViewById(R.id.text_input_end_icon)) { + binding.customPanel.setVisibility( + binding.customPanel.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE + ); } } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.kt b/app/src/main/java/awais/instagrabber/utils/Constants.kt index 82b1f87e..7710379e 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.kt +++ b/app/src/main/java/awais/instagrabber/utils/Constants.kt @@ -90,4 +90,5 @@ object Constants { const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title" const val X_IG_APP_ID = "936619743392459" const val EXTRA_INITIAL_URI = "initial_uri" + const val defaultDateTimeFormat = "hh:mm:ss a 'on' dd-MM-yyyy" } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DateUtils.kt b/app/src/main/java/awais/instagrabber/utils/DateUtils.kt index 474d4eb8..299c1f71 100644 --- a/app/src/main/java/awais/instagrabber/utils/DateUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/DateUtils.kt @@ -1,6 +1,9 @@ package awais.instagrabber.utils +import android.util.Log +import awais.instagrabber.utils.extensions.TAG import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import java.util.* object DateUtils { @@ -14,4 +17,13 @@ object DateUtils { fun isBeforeOrEqual(localDateTime: LocalDateTime, comparedTo: LocalDateTime): Boolean { return localDateTime.isBefore(comparedTo) || localDateTime.isEqual(comparedTo) } + + @JvmStatic + fun checkFormatterValid(datetimeParser: DateTimeFormatter): Boolean = try { + LocalDateTime.now().format(datetimeParser) + true + } catch (e: Exception) { + Log.e(TAG, "checkFormatterValid: ", e) + false + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt index 57f93af3..14c8d1f5 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt @@ -37,7 +37,9 @@ class SettingsHelper(context: Context) { } private fun getStringDefault(@StringSettings key: String): String { - if (PreferenceKeys.DATE_TIME_FORMAT == key) return "hh:mm:ss a 'on' dd-MM-yyyy" + if (PreferenceKeys.DATE_TIME_FORMAT == key) { + return Constants.defaultDateTimeFormat + } return if (PreferenceKeys.DATE_TIME_SELECTION == key) "0;3;0" else "" } diff --git a/app/src/main/java/awais/instagrabber/utils/TextUtils.kt b/app/src/main/java/awais/instagrabber/utils/TextUtils.kt index b2a28f75..a2134179 100644 --- a/app/src/main/java/awais/instagrabber/utils/TextUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/TextUtils.kt @@ -11,11 +11,11 @@ import java.util.* import kotlin.math.absoluteValue object TextUtils { - lateinit var datetimeParser: DateTimeFormatter + var dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(Constants.defaultDateTimeFormat) @JvmStatic fun isEmpty(charSequence: CharSequence?): Boolean { - if (charSequence == null || charSequence.length < 1) return true + if (charSequence.isNullOrBlank()) return true if (charSequence is String) { var str = charSequence if ("" == str || "null" == str || str.isEmpty()) return true @@ -78,7 +78,8 @@ object TextUtils { @JvmStatic fun setFormatter(datetimeParser: DateTimeFormatter) { - this.datetimeParser = datetimeParser + if (!DateUtils.checkFormatterValid(datetimeParser)) return + this.dateTimeFormatter = datetimeParser } @JvmStatic @@ -86,11 +87,11 @@ object TextUtils { return LocalDateTime.ofInstant( Instant.ofEpochSecond(epochSecond), ZoneId.systemDefault() - ).format(datetimeParser) + ).format(dateTimeFormatter) } @JvmStatic fun nowToString(): String { - return LocalDateTime.now().format(datetimeParser) + return LocalDateTime.now().format(dateTimeFormatter) } } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_time_settings.xml b/app/src/main/res/layout/dialog_time_settings.xml index f95af99f..2d0fda2f 100755 --- a/app/src/main/res/layout/dialog_time_settings.xml +++ b/app/src/main/res/layout/dialog_time_settings.xml @@ -33,27 +33,29 @@ android:gravity="center" /> - - - + + + android:autofillHints="no" + android:inputType="text" + android:maxLength="50" + android:padding="16dp" + tools:text="test" /> - - + Share linkā€¦ Slide to Cancel Disable screen transitions + Invalid format