diff --git a/app/src/main/java/com/best/deskclock/data/SettingsDAO.java b/app/src/main/java/com/best/deskclock/data/SettingsDAO.java
index b06ff353b..be9c53ed6 100644
--- a/app/src/main/java/com/best/deskclock/data/SettingsDAO.java
+++ b/app/src/main/java/com/best/deskclock/data/SettingsDAO.java
@@ -484,6 +484,14 @@ public final class SettingsDAO {
return Integer.parseInt(string) * 60;
}
+ /**
+ * @return the timer creation view style.
+ */
+ public static String getTimerCreationViewStyle(SharedPreferences prefs) {
+ // Default value must match the one in res/xml/settings_timer.xml
+ return prefs.getString(KEY_TIMER_CREATION_VIEW_STYLE, DEFAULT_TIMER_CREATION_VIEW_STYLE);
+ }
+
/**
* @return {@code true} if the timer background must be transparent. {@code false} otherwise.
*/
diff --git a/app/src/main/java/com/best/deskclock/settings/PreferencesDefaultValues.java b/app/src/main/java/com/best/deskclock/settings/PreferencesDefaultValues.java
index b69127714..8899361ee 100644
--- a/app/src/main/java/com/best/deskclock/settings/PreferencesDefaultValues.java
+++ b/app/src/main/java/com/best/deskclock/settings/PreferencesDefaultValues.java
@@ -102,6 +102,8 @@ public class PreferencesDefaultValues {
}
// Timer
+ public static final String DEFAULT_TIMER_CREATION_VIEW_STYLE = "keypad";
+ public static final String TIMER_CREATION_VIEW_SPINNER_STYLE = "spinner";
public static final String DEFAULT_TIMER_AUTO_SILENCE = "30";
public static final String DEFAULT_TIMER_CRESCENDO_DURATION = "0";
public static final boolean DEFAULT_TIMER_VIBRATE = false;
diff --git a/app/src/main/java/com/best/deskclock/settings/PreferencesKeys.java b/app/src/main/java/com/best/deskclock/settings/PreferencesKeys.java
index dcb773c23..8d599aba5 100644
--- a/app/src/main/java/com/best/deskclock/settings/PreferencesKeys.java
+++ b/app/src/main/java/com/best/deskclock/settings/PreferencesKeys.java
@@ -106,6 +106,7 @@ public class PreferencesKeys {
public static final String KEY_PREVIEW_ALARM = "key_preview_alarm";
// Timer
+ public static final String KEY_TIMER_CREATION_VIEW_STYLE = "key_timer_creation_view_style";
public static final String KEY_TIMER_RINGTONE = "key_timer_ringtone";
public static final String KEY_TIMER_AUTO_SILENCE = "key_timer_auto_silence";
public static final String KEY_TIMER_CRESCENDO_DURATION = "key_timer_crescendo_duration";
diff --git a/app/src/main/java/com/best/deskclock/settings/TimerSettingsFragment.java b/app/src/main/java/com/best/deskclock/settings/TimerSettingsFragment.java
index aafceb9f1..3c0b4dd9a 100644
--- a/app/src/main/java/com/best/deskclock/settings/TimerSettingsFragment.java
+++ b/app/src/main/java/com/best/deskclock/settings/TimerSettingsFragment.java
@@ -7,6 +7,7 @@ import static com.best.deskclock.settings.PreferencesKeys.KEY_DEFAULT_TIME_TO_AD
import static com.best.deskclock.settings.PreferencesKeys.KEY_DISPLAY_WARNING_BEFORE_DELETING_TIMER;
import static com.best.deskclock.settings.PreferencesKeys.KEY_SORT_TIMER;
import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_AUTO_SILENCE;
+import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_CREATION_VIEW_STYLE;
import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_CRESCENDO_DURATION;
import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_FLIP_ACTION;
import static com.best.deskclock.settings.PreferencesKeys.KEY_TIMER_POWER_BUTTON_ACTION;
@@ -40,6 +41,7 @@ public class TimerSettingsFragment extends ScreenFragment
ListPreference mTimerCrescendoPref;
ListPreference mSortTimerPref;
ListPreference mDefaultMinutesToAddToTimerPref;
+ ListPreference mTimerCreationViewStylePref;
Preference mTimerRingtonePref;
Preference mTimerVibratePref;
SwitchPreferenceCompat mTimerVolumeButtonsActionPref;
@@ -74,6 +76,7 @@ public class TimerSettingsFragment extends ScreenFragment
mDefaultMinutesToAddToTimerPref = findPreference(KEY_DEFAULT_TIME_TO_ADD_TO_TIMER);
mTransparentBackgroundPref = findPreference(KEY_TRANSPARENT_BACKGROUND_FOR_EXPIRED_TIMER);
mDisplayWarningBeforeDeletingTimerPref = findPreference(KEY_DISPLAY_WARNING_BEFORE_DELETING_TIMER);
+ mTimerCreationViewStylePref = findPreference(KEY_TIMER_CREATION_VIEW_STYLE);
setupPreferences();
}
@@ -90,7 +93,8 @@ public class TimerSettingsFragment extends ScreenFragment
switch (pref.getKey()) {
case KEY_TIMER_RINGTONE -> mTimerRingtonePref.setSummary(DataModel.getDataModel().getTimerRingtoneTitle());
- case KEY_TIMER_AUTO_SILENCE, KEY_TIMER_CRESCENDO_DURATION, KEY_DEFAULT_TIME_TO_ADD_TO_TIMER -> {
+ case KEY_TIMER_AUTO_SILENCE, KEY_TIMER_CRESCENDO_DURATION,
+ KEY_DEFAULT_TIME_TO_ADD_TO_TIMER, KEY_TIMER_CREATION_VIEW_STYLE -> {
final ListPreference preference = (ListPreference) pref;
final int index = preference.findIndexOfValue((String) newValue);
preference.setSummary(preference.getEntries()[index]);
@@ -148,6 +152,9 @@ public class TimerSettingsFragment extends ScreenFragment
mTimerPowerButtonActionPref.setOnPreferenceChangeListener(this);
+ mTimerCreationViewStylePref.setOnPreferenceChangeListener(this);
+ mTimerCreationViewStylePref.setSummary(mTimerCreationViewStylePref.getEntry());
+
SensorManager sensorManager = (SensorManager) requireActivity().getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) == null) {
mTimerFlipActionPref.setChecked(false);
diff --git a/app/src/main/java/com/best/deskclock/timer/CustomTimerSpinnerSetupView.java b/app/src/main/java/com/best/deskclock/timer/CustomTimerSpinnerSetupView.java
new file mode 100644
index 000000000..1c142e427
--- /dev/null
+++ b/app/src/main/java/com/best/deskclock/timer/CustomTimerSpinnerSetupView.java
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-3.0-only
+
+package com.best.deskclock.timer;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.NumberPicker;
+
+import androidx.annotation.Nullable;
+
+import com.best.deskclock.R;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom component to display a time selection view using spinners used when creating timers.
+ */
+public class CustomTimerSpinnerSetupView extends LinearLayout {
+ NumberPicker mHourPicker;
+ NumberPicker mMinutePicker;
+ NumberPicker mSecondPicker;
+
+ @Nullable
+ OnValueChangeListener mOnValueChangeListener;
+
+ public CustomTimerSpinnerSetupView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+
+ View rootView = inflate(context, R.layout.timer_spinner_setup_view, this);
+ mHourPicker = rootView.findViewById(R.id.hour);
+ mMinutePicker = rootView.findViewById(R.id.minute);
+ mSecondPicker = rootView.findViewById(R.id.second);
+
+ setupCustomSpinnerDurationPicker();
+ }
+
+ private void setupCustomSpinnerDurationPicker() {
+ mHourPicker.setMinValue(0);
+ mHourPicker.setMaxValue(24);
+
+ mMinutePicker.setMinValue(0);
+ mMinutePicker.setMaxValue(59);
+
+ mSecondPicker.setMinValue(0);
+ mSecondPicker.setMaxValue(59);
+
+ mHourPicker.setOnValueChangedListener((_picker, _oldVal, _newVal) -> {
+ if (mOnValueChangeListener != null) mOnValueChangeListener.onValueChange(getValue());
+ });
+
+ mMinutePicker.setOnValueChangedListener((_picker, _oldVal, _newVal) -> {
+ if (mOnValueChangeListener != null) mOnValueChangeListener.onValueChange(getValue());
+ });
+
+ mSecondPicker.setOnValueChangedListener((_picker, _oldVal, _newVal) -> {
+ if (mOnValueChangeListener != null) mOnValueChangeListener.onValueChange(getValue());
+ });
+ }
+
+ public void setValue(long valueMillis) {
+ long hours = TimeUnit.MILLISECONDS.toHours(valueMillis);
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(valueMillis) % 60;
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(valueMillis) % 60;
+ setValue(new DurationObject((int) hours, (int) minutes, (int) seconds));
+ }
+
+ public void setValue(DurationObject value) {
+ mHourPicker.setValue(value.hour());
+ mMinutePicker.setValue(value.minute());
+ mSecondPicker.setValue(value.second());
+ }
+
+ public void reset() {
+ setValue(new DurationObject(0, 0, 0));
+ }
+
+ public DurationObject getValue() {
+ return new DurationObject(mHourPicker.getValue(), mMinutePicker.getValue(), mSecondPicker.getValue());
+ }
+
+ public void setOnChangeListener(OnValueChangeListener onValueChangeListener) {
+ this.mOnValueChangeListener = onValueChangeListener;
+ }
+
+ public interface OnValueChangeListener {
+ void onValueChange(DurationObject duration);
+ }
+
+ public record DurationObject(int hour, int minute, int second) {
+ public long toMillis() {
+ return (((hour * 60L) + minute) * 60 + second) * 1000;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/best/deskclock/timer/TimerFragment.java b/app/src/main/java/com/best/deskclock/timer/TimerFragment.java
index 705628a1b..482cbbd21 100644
--- a/app/src/main/java/com/best/deskclock/timer/TimerFragment.java
+++ b/app/src/main/java/com/best/deskclock/timer/TimerFragment.java
@@ -13,6 +13,7 @@ import static android.view.View.TRANSLATION_Y;
import static android.view.View.VISIBLE;
import static com.best.deskclock.DeskClockApplication.getDefaultSharedPreferences;
+import static com.best.deskclock.settings.PreferencesDefaultValues.TIMER_CREATION_VIEW_SPINNER_STYLE;
import static com.best.deskclock.uidata.UiDataModel.Tab.TIMERS;
import android.animation.Animator;
@@ -68,6 +69,7 @@ public final class TimerFragment extends DeskClockFragment {
private RecyclerView mRecyclerView;
private Serializable mTimerSetupState;
private TimerSetupView mCreateTimerView;
+ private CustomTimerSpinnerSetupView mCreateTimerSpinnerView;
private TimerAdapter mAdapter;
private View mTimersView;
private View mCurrentView;
@@ -114,6 +116,7 @@ public final class TimerFragment extends DeskClockFragment {
mRecyclerView = view.findViewById(R.id.recycler_view);
mTimersView = view.findViewById(R.id.timer_view);
mCreateTimerView = view.findViewById(R.id.timer_setup);
+ mCreateTimerSpinnerView = view.findViewById(R.id.timer_spinner_setup);
mIsTablet = ThemeUtils.isTablet();
mIsLandscape = ThemeUtils.isLandscape();
@@ -127,6 +130,8 @@ public final class TimerFragment extends DeskClockFragment {
mRecyclerView.setClipToPadding(false);
mCreateTimerView.setFabContainer(this);
+ mCreateTimerSpinnerView.setOnChangeListener((durationObject) ->
+ updateFab(FAB_SHRINK_AND_EXPAND));
DataModel.getDataModel().addTimerListener(mAdapter);
DataModel.getDataModel().addTimerListener(mTimerWatcher);
@@ -187,7 +192,7 @@ public final class TimerFragment extends DeskClockFragment {
getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
- if (isTabSelected() && mCurrentView == mCreateTimerView && hasTimers()) {
+ if (isTabSelected() && mCurrentView != mTimersView && hasTimers()) {
animateToView(mTimersView, false);
} else {
setEnabled(false);
@@ -218,7 +223,7 @@ public final class TimerFragment extends DeskClockFragment {
super.onSaveInstanceState(outState);
// If the timer creation view is visible, store the input for later restoration.
- if (mCurrentView == mCreateTimerView) {
+ if (mCurrentView != mTimersView) {
mTimerSetupState = mCreateTimerView.getState();
outState.putSerializable(KEY_TIMER_SETUP_STATE, mTimerSetupState);
}
@@ -230,8 +235,8 @@ public final class TimerFragment extends DeskClockFragment {
fab.setImageResource(R.drawable.ic_add);
fab.setContentDescription(mContext.getString(R.string.timer_add_timer));
fab.setVisibility(VISIBLE);
- } else if (mCurrentView == mCreateTimerView) {
- if (mCreateTimerView.hasValidInput()) {
+ } else if (mCurrentView == getTimerCreationView()) {
+ if (hasValidInput()) {
fab.setImageResource(R.drawable.ic_fab_play);
fab.setContentDescription(mContext.getString(R.string.timer_start));
fab.setVisibility(VISIBLE);
@@ -243,6 +248,34 @@ public final class TimerFragment extends DeskClockFragment {
}
}
+ private boolean isSpinnerCreationView() {
+ return SettingsDAO.getTimerCreationViewStyle(mPrefs).equals(TIMER_CREATION_VIEW_SPINNER_STYLE);
+ }
+
+ private boolean hasValidInput() {
+ if (isSpinnerCreationView()) {
+ return mCreateTimerSpinnerView.getValue().toMillis() != 0;
+ } else {
+ return mCreateTimerView.hasValidInput();
+ }
+ }
+
+ private long getTimeInMillis() {
+ if (isSpinnerCreationView()) {
+ return mCreateTimerSpinnerView.getValue().toMillis();
+ } else {
+ return mCreateTimerView.getTimeInMillis();
+ }
+ }
+
+ private View getTimerCreationView() {
+ if (isSpinnerCreationView()) {
+ return mCreateTimerSpinnerView;
+ } else {
+ return mCreateTimerView;
+ }
+ }
+
@Override
public void onUpdateFab(@NonNull ImageView fab) {
updateFab(fab);
@@ -262,7 +295,7 @@ public final class TimerFragment extends DeskClockFragment {
left.setVisibility(INVISIBLE);
right.setVisibility(INVISIBLE);
- } else if (mCurrentView == mCreateTimerView) {
+ } else if (mCurrentView == getTimerCreationView()) {
right.setVisibility(INVISIBLE);
left.setClickable(true);
@@ -272,6 +305,7 @@ public final class TimerFragment extends DeskClockFragment {
left.setVisibility(hasTimers() ? VISIBLE : INVISIBLE);
left.setOnClickListener(v -> {
mCreateTimerView.reset();
+ mCreateTimerSpinnerView.reset();
animateToView(mTimersView, false);
left.announceForAccessibility(mContext.getString(R.string.timer_canceled));
Utils.setVibrationTime(mContext, 10);
@@ -282,12 +316,12 @@ public final class TimerFragment extends DeskClockFragment {
@Override
public void onFabClick(@NonNull ImageView fab) {
if (mCurrentView == mTimersView) {
- animateToView(mCreateTimerView, true);
- } else if (mCurrentView == mCreateTimerView) {
+ animateToView(getTimerCreationView(), true);
+ } else if (mCurrentView == getTimerCreationView()) {
mCreatingTimer = true;
try {
// Create the new timer.
- final long timerLength = mCreateTimerView.getTimeInMillis();
+ final long timerLength = getTimeInMillis();
String defaultTimeToAddToTimer = String.valueOf(SettingsDAO.getDefaultTimeToAddToTimer(mPrefs));
final Timer timer = DataModel.getDataModel().addTimer(timerLength, "",
defaultTimeToAddToTimer, false);
@@ -323,10 +357,14 @@ public final class TimerFragment extends DeskClockFragment {
// Show the creation view; hide the timer view.
mTimersView.setVisibility(GONE);
- mCreateTimerView.setVisibility(VISIBLE);
+
+ // Reset all possible time picker views to be hidden in order to only show one of them later
+ mCreateTimerView.setVisibility(GONE);
+ mCreateTimerSpinnerView.setVisibility(GONE);
// Record the fact that the create view is visible.
- mCurrentView = mCreateTimerView;
+ mCurrentView = getTimerCreationView();
+ mCurrentView.setVisibility(VISIBLE);
// Update the fab and buttons.
updateFab(updateTypes);
@@ -342,6 +380,7 @@ public final class TimerFragment extends DeskClockFragment {
// Show the timer view; hide the creation view.
mTimersView.setVisibility(VISIBLE);
mCreateTimerView.setVisibility(GONE);
+ mCreateTimerSpinnerView.setVisibility(GONE);
// Record the fact that the create view is visible.
mCurrentView = mTimersView;
@@ -366,7 +405,7 @@ public final class TimerFragment extends DeskClockFragment {
if (toTimers) {
mTimersView.setVisibility(VISIBLE);
} else {
- mCreateTimerView.setVisibility(VISIBLE);
+ getTimerCreationView().setVisibility(VISIBLE);
}
// Avoid double-taps by enabling/disabling the set of buttons active on the new view.
updateFab(BUTTONS_DISABLE);
@@ -415,6 +454,7 @@ public final class TimerFragment extends DeskClockFragment {
// Reset the state of the create view.
mCreateTimerView.reset();
+ mCreateTimerSpinnerView.reset();
} else {
showCreateTimerView(FAB_AND_BUTTONS_EXPAND);
}
@@ -437,8 +477,10 @@ public final class TimerFragment extends DeskClockFragment {
super.onAnimationEnd(animation);
mTimersView.setTranslationY(0f);
mCreateTimerView.setTranslationY(0f);
+ mCreateTimerSpinnerView.setTranslationY(0f);
mTimersView.setAlpha(1f);
mCreateTimerView.setAlpha(1f);
+ mCreateTimerSpinnerView.setAlpha(1f);
}
});
@@ -543,7 +585,7 @@ public final class TimerFragment extends DeskClockFragment {
updateFab(FAB_AND_BUTTONS_IMMEDIATE);
if (mCurrentView == mTimersView && mAdapter.getItemCount() == 0) {
- animateToView(mCreateTimerView, true);
+ animateToView(getTimerCreationView(), true);
}
// Required to adjust the layout for tablets that use either a GridLayoutManager or a LinearLayoutManager.
diff --git a/app/src/main/res/layout/timer_fragment.xml b/app/src/main/res/layout/timer_fragment.xml
index 131f6cfed..b8f3d7429 100644
--- a/app/src/main/res/layout/timer_fragment.xml
+++ b/app/src/main/res/layout/timer_fragment.xml
@@ -29,4 +29,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/timer_spinner_setup_view.xml b/app/src/main/res/layout/timer_spinner_setup_view.xml
new file mode 100644
index 000000000..31f087051
--- /dev/null
+++ b/app/src/main/res/layout/timer_spinner_setup_view.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 5b8952f49..14c06cd56 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -407,6 +407,18 @@
- spinner
+
+
+ - @string/timer_creation_view_style_keypad
+ - @string/clock_style_spinner
+
+
+
+ - keypad
+ - spinner
+
+
- @string/sort_timer_manually
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eb2791971..8330caa8e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1112,6 +1112,10 @@
Timers
Set default ringtone, volume and timer sorting
+
+ Timer creation view style
+
+ Keypad
Timer vibrate
diff --git a/app/src/main/res/xml/settings_timer.xml b/app/src/main/res/xml/settings_timer.xml
index e1ab0f5e8..a2d6c3154 100644
--- a/app/src/main/res/xml/settings_timer.xml
+++ b/app/src/main/res/xml/settings_timer.xml
@@ -14,6 +14,16 @@
app:iconSpaceReserved="false"
tools:layout="@layout/settings_preference_category_layout">
+
+