Allow the creation of timers with spinner

This commit is contained in:
Bnyro 2025-06-13 13:17:59 +02:00 committed by GitHub
parent f220776d2c
commit e995e9a000
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 294 additions and 13 deletions

View file

@ -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.
*/

View file

@ -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;

View file

@ -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";

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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.

View file

@ -29,4 +29,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.best.deskclock.timer.CustomTimerSpinnerSetupView
android:id="@+id/timer_spinner_setup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>

View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
modified
SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/durationPickerLayout"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:paddingTop="20dp">
<!-- hour -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_hours"
android:textColor="?attr/colorPrimary" />
<NumberPicker
android:id="@+id/hour"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:theme="@style/NumberPickerStyle" />
</LinearLayout>
<!-- minute -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="14dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_minutes"
android:textColor="?attr/colorPrimary" />
<NumberPicker
android:id="@+id/minute"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:theme="@style/NumberPickerStyle" />
</LinearLayout>
<!-- second -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_seconds"
android:textColor="?attr/colorPrimary" />
<NumberPicker
android:id="@+id/second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:theme="@style/NumberPickerStyle" />
</LinearLayout>
</LinearLayout>

View file

@ -407,6 +407,18 @@
<item>spinner</item>
</string-array>
<!-- Entries listed in the ListPreference when invoking the timer creation view style preference. -->
<string-array name="timer_creation_view_style_entries">
<item>@string/timer_creation_view_style_keypad</item>
<item>@string/clock_style_spinner</item>
</string-array>
<!-- Values that are retrieved from the ListPreference. These must match the
timer_creation_view_style_entries above. -->
<string-array name="timer_creation_view_style_values" translatable="false">
<item>keypad</item>
<item>spinner</item>
</string-array>
<!-- Entries listed in the ListPreference when invoking the timer sort preference -->
<string-array name="sort_timer_entries">
<item>@string/sort_timer_manually</item>

View file

@ -1112,6 +1112,10 @@
<string name="timer_settings">Timers</string>
<!-- Timer preference summary in the settings -->
<string name="timer_settings_summary">Set default ringtone, volume and timer sorting</string>
<!-- Title for setting the timer creation view style -->
<string name="timer_creation_view_style_title">Timer creation view style</string>
<!-- Keypad style when creating timer -->
<string name="timer_creation_view_style_keypad">Keypad</string>
<!-- Description for timer vibration. -->
<string name="timer_vibrate_title">Timer vibrate</string>
<!-- Setting title to reset expired timers with volume buttons. -->

View file

@ -14,6 +14,16 @@
app:iconSpaceReserved="false"
tools:layout="@layout/settings_preference_category_layout">
<ListPreference
android:key="key_timer_creation_view_style"
android:title="@string/timer_creation_view_style_title"
android:entries="@array/timer_creation_view_style_entries"
android:entryValues="@array/timer_creation_view_style_values"
android:defaultValue="keypad"
app:iconSpaceReserved="false"
app:singleLineTitle="false"
tools:layout="@layout/settings_preference_layout" />
<SwitchPreferenceCompat
android:key="key_transparent_background_for_expired_timer"
android:title="@string/transparent_background_for_expired_timer_title"