Ability to set a new duration to an existing timer - Fix #76

- Doable by long-pressing time;
- Only possible when the timer is stopped;
This commit is contained in:
BlackyHawky 2025-06-02 18:09:50 +02:00
parent 34846c73ef
commit fc8d5c6dcb
25 changed files with 651 additions and 50 deletions

View file

@ -478,6 +478,15 @@ public final class DataModel {
mTimerModel.updateTimer(timer.setLabel(label));
}
/**
* @param timer the timer to which the new {@code newLength} belongs
* @param newLength the new duration to store for the {@code timer}
*/
public void setNewTimerDuration(Timer timer, long newLength) {
enforceMainLooper();
mTimerModel.updateTimer(timer.setNewDuration(newLength));
}
/**
* @param timer the timer to which the new {@code buttonTime} belongs
* @param buttonTime the new add button text to store for the {@code timer}

View file

@ -178,6 +178,23 @@ public final class Timer {
mLastStartWallClockTime, mRemainingTime, label, mButtonTime, mDeleteAfterUse);
}
/**
* @return a copy of this timer with the given {@code newLength}
*/
Timer setNewDuration(long newLength) {
if (mState != State.RESET) {
return this;
}
if (mLength == newLength) {
return this;
}
return new Timer(mId, mState, newLength, newLength, mLastStartTime, mLastStartWallClockTime,
newLength, mLabel, mButtonTime, mDeleteAfterUse
);
}
/**
* @return a copy of this timer with the given button time
*/

View file

@ -95,7 +95,7 @@ public class TimerAddTimeButtonDialogFragment extends DialogFragment {
final FragmentTransaction tx = manager.beginTransaction();
// Remove existing instance of LabelDialogFragment if necessary.
// Remove existing instance of this DialogFragment if necessary.
final Fragment existing = manager.findFragmentByTag(TAG);
if (existing != null) {
tx.remove(existing);
@ -108,7 +108,7 @@ public class TimerAddTimeButtonDialogFragment extends DialogFragment {
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// As long as the add time button box exists, save its state.
// As long as this dialog exists, save its state.
if (mEditMinutes != null && mEditSeconds != null) {
outState.putString(ARG_EDIT_MINUTES, Objects.requireNonNull(mEditMinutes.getText()).toString());
outState.putString(ARG_EDIT_SECONDS, Objects.requireNonNull(mEditSeconds.getText()).toString());
@ -186,7 +186,7 @@ public class TimerAddTimeButtonDialogFragment extends DialogFragment {
new MaterialAlertDialogBuilder(requireContext())
.setIcon(drawable)
.setTitle(isInvalidInput(inputMinutesText, inputSecondsText)
? getString(R.string.timer_button_time_warning_box_title)
? getString(R.string.timer_time_warning_box_title)
: getString(R.string.timer_button_time_box_title))
.setView(view)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
@ -285,7 +285,7 @@ public class TimerAddTimeButtonDialogFragment extends DialogFragment {
AlertDialog alertDialog = (AlertDialog) requireDialog();
alertDialog.setIcon(drawable);
alertDialog.setTitle(getString(R.string.timer_button_time_warning_box_title));
alertDialog.setTitle(getString(R.string.timer_time_warning_box_title));
String minutesText = Objects.requireNonNull(mEditMinutes.getText()).toString();
String secondsText = Objects.requireNonNull(mEditSeconds.getText()).toString();

View file

@ -32,9 +32,15 @@ public final class TimerClickHandler {
}
public void onEditAddTimeButtonLongClicked(Timer timer) {
Events.sendAlarmEvent(R.string.action_add_custom_time_to_timer, R.string.label_deskclock);
Events.sendTimerEvent(R.string.action_add_custom_time_to_timer, R.string.label_deskclock);
final TimerAddTimeButtonDialogFragment fragment = TimerAddTimeButtonDialogFragment.newInstance(timer);
TimerAddTimeButtonDialogFragment.show(mFragment.getParentFragmentManager(), fragment);
}
public void onDurationClicked(Timer timer) {
Events.sendTimerEvent(R.string.action_set_new_timer_duration, R.string.label_deskclock);
final TimerSetNewDurationDialogFragment fragment = TimerSetNewDurationDialogFragment.newInstance(timer);
TimerSetNewDurationDialogFragment.show(mFragment.getParentFragmentManager(), fragment);
}
}

View file

@ -0,0 +1,461 @@
// SPDX-License-Identifier: GPL-3.0-only
package com.best.deskclock.timer;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
import android.app.Dialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.best.deskclock.R;
import com.best.deskclock.data.DataModel;
import com.best.deskclock.data.Timer;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* DialogFragment to set a new duration to the timer.
*/
public class TimerSetNewDurationDialogFragment extends DialogFragment {
/**
* The tag that identifies instances of TimerSetNewDurationDialogFragment in the fragment manager.
*/
private static final String TAG = "set_new_duration_dialog";
private static final String ARG_EDIT_HOURS = "arg_edit_hours";
private static final String ARG_EDIT_MINUTES = "arg_edit_minutes";
private static final String ARG_EDIT_SECONDS = "arg_edit_seconds";
private static final String ARG_TIMER_ID = "arg_timer_id";
private TextInputLayout mHoursInputLayout;
private TextInputLayout mMinutesInputLayout;
private TextInputLayout mSecondsInputLayout;
private EditText mEditHours;
private EditText mEditMinutes;
private EditText mEditSeconds;
private int mTimerId;
private InputMethodManager mInput;
public static TimerSetNewDurationDialogFragment newInstance(Timer timer) {
final Bundle args = new Bundle();
long remainingTime = timer.getRemainingTime();
long hours = TimeUnit.MILLISECONDS.toHours(remainingTime);
long minutes = TimeUnit.MILLISECONDS.toMinutes(remainingTime) % 60;
long seconds = TimeUnit.MILLISECONDS.toSeconds(remainingTime) % 60;
args.putLong(ARG_EDIT_HOURS, hours);
args.putLong(ARG_EDIT_MINUTES, minutes);
args.putLong(ARG_EDIT_SECONDS, seconds);
args.putInt(ARG_TIMER_ID, timer.getId());
final TimerSetNewDurationDialogFragment frag = new TimerSetNewDurationDialogFragment();
frag.setArguments(args);
return frag;
}
/**
* Replaces any existing TimerSetNewDurationDialogFragment with the given {@code fragment}.
*/
public static void show(FragmentManager manager, TimerSetNewDurationDialogFragment fragment) {
if (manager == null || manager.isDestroyed()) {
return;
}
// Finish any outstanding fragment work.
manager.executePendingTransactions();
final FragmentTransaction tx = manager.beginTransaction();
// Remove existing instance of this DialogFragment if necessary.
final Fragment existing = manager.findFragmentByTag(TAG);
if (existing != null) {
tx.remove(existing);
}
tx.addToBackStack(null);
fragment.show(tx, TAG);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// As long as this dialog exists, save its state.
if (mEditHours != null && mEditMinutes != null && mEditSeconds != null) {
outState.putString(ARG_EDIT_HOURS, Objects.requireNonNull(mEditHours.getText()).toString());
outState.putString(ARG_EDIT_MINUTES, Objects.requireNonNull(mEditMinutes.getText()).toString());
outState.putString(ARG_EDIT_SECONDS, Objects.requireNonNull(mEditSeconds.getText()).toString());
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments() == null ? Bundle.EMPTY : getArguments();
mTimerId = args.getInt(ARG_TIMER_ID, -1);
long editHours = args.getLong(ARG_EDIT_HOURS, 0);
long editMinutes = args.getLong(ARG_EDIT_MINUTES, 0);
long editSeconds = args.getLong(ARG_EDIT_SECONDS, 0);
if (savedInstanceState != null) {
editHours = savedInstanceState.getLong(ARG_EDIT_HOURS, editHours);
editMinutes = savedInstanceState.getLong(ARG_EDIT_MINUTES, editMinutes);
editSeconds = savedInstanceState.getLong(ARG_EDIT_SECONDS, editSeconds);
}
View view = LayoutInflater.from(requireContext()).inflate(R.layout.timer_dialog_edit_new_time, null);
mHoursInputLayout = view.findViewById(R.id.dialog_input_layout_hours);
mHoursInputLayout.setHelperText(getString(R.string.timer_hours_warning_box_text));
mMinutesInputLayout = view.findViewById(R.id.dialog_input_layout_minutes);
mMinutesInputLayout.setHelperText(getString(R.string.timer_minutes_warning_box_text));
mSecondsInputLayout = view.findViewById(R.id.dialog_input_layout_seconds);
mSecondsInputLayout.setHelperText(getString(R.string.timer_seconds_warning_box_text));
mEditHours = view.findViewById(R.id.edit_hours);
mEditMinutes = view.findViewById(R.id.edit_minutes);
mEditSeconds = view.findViewById(R.id.edit_seconds);
mEditHours.setText(String.valueOf(editHours));
if (editHours == 24) {
mEditHours.setImeOptions(EditorInfo.IME_ACTION_DONE);
mEditHours.setOnEditorActionListener(new ImeDoneListener());
mEditMinutes.setEnabled(false);
mEditSeconds.setEnabled(false);
} else {
mEditHours.setImeOptions(EditorInfo.IME_ACTION_NEXT);
mEditMinutes.setEnabled(true);
mEditMinutes.setImeOptions(EditorInfo.IME_ACTION_NEXT & EditorInfo.IME_ACTION_PREVIOUS);
mEditSeconds.setEnabled(true);
}
mEditHours.setInputType(InputType.TYPE_CLASS_NUMBER);
mEditHours.selectAll();
mEditHours.requestFocus();
mEditHours.addTextChangedListener(new TextChangeListener());
mEditHours.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
mEditHours.selectAll();
}
});
mEditMinutes.setText(String.valueOf(editMinutes));
mEditMinutes.selectAll();
mEditMinutes.setInputType(InputType.TYPE_CLASS_NUMBER);
mEditMinutes.addTextChangedListener(new TextChangeListener());
mEditMinutes.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
mEditMinutes.selectAll();
}
});
mEditSeconds.setText(String.valueOf(editSeconds));
mEditSeconds.selectAll();
mEditSeconds.setInputType(InputType.TYPE_CLASS_NUMBER);
mEditSeconds.setOnEditorActionListener(new ImeDoneListener());
mEditSeconds.addTextChangedListener(new TextChangeListener());
mEditSeconds.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
mEditSeconds.selectAll();
}
});
String inputHoursText = mEditHours.getText().toString();
String inputMinutesText = mEditMinutes.getText().toString();
String inputSecondsText = mEditSeconds.getText().toString();
mInput = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE);
final Drawable drawable = AppCompatResources.getDrawable(requireContext(),
isInvalidInput(inputHoursText, inputMinutesText, inputSecondsText)
? R.drawable.ic_error
: R.drawable.ic_hourglass_top);
if (drawable != null) {
drawable.setTint(MaterialColors.getColor(
requireContext(), com.google.android.material.R.attr.colorOnSurface, Color.BLACK));
}
final MaterialAlertDialogBuilder dialogBuilder =
new MaterialAlertDialogBuilder(requireContext())
.setIcon(drawable)
.setTitle(isInvalidInput(inputHoursText, inputMinutesText, inputSecondsText)
? getString(R.string.timer_time_warning_box_title)
: getString(R.string.timer_time_box_title))
.setView(view)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
if (isInvalidInput(inputHoursText, inputMinutesText, inputSecondsText)) {
updateDialogForInvalidInput();
} else {
setNewDuration();
dismiss();
}
})
.setNegativeButton(android.R.string.cancel, null);
final AlertDialog dialog = dialogBuilder.create();
final Window alertDialogWindow = dialog.getWindow();
if (alertDialogWindow != null) {
alertDialogWindow.setSoftInputMode(SOFT_INPUT_ADJUST_PAN | SOFT_INPUT_STATE_VISIBLE);
}
return dialog;
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Stop callbacks from the IME since there is no view to process them.
mEditHours.setOnEditorActionListener(null);
mEditMinutes.setOnEditorActionListener(null);
mEditSeconds.setOnEditorActionListener(null);
}
/**
* Sets the new duration to the timer.
*/
private void setNewDuration() {
String hoursText = Objects.requireNonNull(mEditHours.getText()).toString();
String minutesText = Objects.requireNonNull(mEditMinutes.getText()).toString();
String secondsText = Objects.requireNonNull(mEditSeconds.getText()).toString();
int hours = 0;
int minutes = 0;
int seconds = 0;
if (!hoursText.isEmpty()) {
hours = Integer.parseInt(hoursText);
}
if (!minutesText.isEmpty()) {
minutes = Integer.parseInt(minutesText);
}
if (!secondsText.isEmpty()) {
seconds = Integer.parseInt(secondsText);
}
if ((hoursText.isEmpty() && minutesText.isEmpty() && secondsText.isEmpty())
|| (hours == 0 && minutes == 0 && seconds == 0)) {
seconds = 1;
}
if (hours == 24) {
minutes = 0;
seconds = 0;
}
if (mTimerId >= 0) {
final Timer timer = DataModel.getDataModel().getTimer(mTimerId);
if (timer != null) {
int totalSeconds = hours * 3600 + minutes * 60 + seconds;
long newLengthMillis = totalSeconds * 1000L;
DataModel.getDataModel().setNewTimerDuration(timer, newLengthMillis);
}
}
}
/**
* @return {@code true} if:
* <ul>
* <li>hours are less than 0 or greater than 24</li>
* <li>minutes are less than 0 or greater than 59</li>
* <li>seconds are less than 0 or greater than 59</li>
* </ul>
* {@code false} otherwise.
*/
private boolean isInvalidInput(String hoursText, String minutesText, String secondsText) {
int hours = 0;
int minutes = 0;
int seconds = 0;
if (!hoursText.isEmpty()) {
hours = Integer.parseInt(hoursText);
}
if (!minutesText.isEmpty()) {
minutes = Integer.parseInt(minutesText);
}
if (!secondsText.isEmpty()) {
seconds = Integer.parseInt(secondsText);
}
return hours < 0 || hours > 24 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59;
}
/**
* Update the dialog icon and title for invalid entries.
* The outline color of the edit box and the hint color are also changed.
*/
private void updateDialogForInvalidInput() {
final Drawable drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_error);
if (drawable != null) {
drawable.setTint(MaterialColors.getColor(
requireContext(), com.google.android.material.R.attr.colorOnSurface, Color.BLACK));
}
AlertDialog alertDialog = (AlertDialog) requireDialog();
alertDialog.setIcon(drawable);
alertDialog.setTitle(getString(R.string.timer_time_warning_box_title));
String hoursText = Objects.requireNonNull(mEditHours.getText()).toString();
String minutesText = Objects.requireNonNull(mEditMinutes.getText()).toString();
String secondsText = Objects.requireNonNull(mEditSeconds.getText()).toString();
boolean hoursInvalid = (!hoursText.isEmpty() && Integer.parseInt(hoursText) < 0)
|| (!hoursText.isEmpty() && Integer.parseInt(hoursText) > 24);
boolean minutesInvalid = (!minutesText.isEmpty() && Integer.parseInt(minutesText) < 0)
|| (!minutesText.isEmpty() && Integer.parseInt(minutesText) > 59);
boolean secondsInvalid = (!secondsText.isEmpty() && Integer.parseInt(secondsText) < 0)
|| (!secondsText.isEmpty() && Integer.parseInt(secondsText) > 59);
int invalidColor = ContextCompat.getColor(requireContext(), R.color.md_theme_error);
int validColor = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorPrimary, Color.BLACK);
mHoursInputLayout.setBoxStrokeColor(hoursInvalid ? invalidColor : validColor);
mHoursInputLayout.setHintTextColor(hoursInvalid
? ColorStateList.valueOf(invalidColor)
: ColorStateList.valueOf(validColor));
mMinutesInputLayout.setBoxStrokeColor(minutesInvalid ? invalidColor : validColor);
mMinutesInputLayout.setHintTextColor(minutesInvalid
? ColorStateList.valueOf(invalidColor)
: ColorStateList.valueOf(validColor));
mSecondsInputLayout.setBoxStrokeColor(secondsInvalid ? invalidColor : validColor);
mSecondsInputLayout.setHintTextColor(secondsInvalid
? ColorStateList.valueOf(invalidColor)
: ColorStateList.valueOf(validColor));
}
/**
* Update the dialog icon and title for valid entries.
* The outline color of the edit box and the hint color are also changed.
*/
private void updateDialogForValidInput() {
final Drawable drawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_hourglass_top);
if (drawable != null) {
drawable.setTint(MaterialColors.getColor(
requireContext(), com.google.android.material.R.attr.colorOnSurface, Color.BLACK));
}
AlertDialog alertDialog = (AlertDialog) requireDialog();
alertDialog.setIcon(drawable);
alertDialog.setTitle(getString(R.string.timer_button_time_box_title));
int validColor = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorPrimary, Color.BLACK);
mHoursInputLayout.setBoxStrokeColor(validColor);
mHoursInputLayout.setHintTextColor(ColorStateList.valueOf(validColor));
mMinutesInputLayout.setBoxStrokeColor(validColor);
mMinutesInputLayout.setHintTextColor(ColorStateList.valueOf(validColor));
mSecondsInputLayout.setBoxStrokeColor(validColor);
mSecondsInputLayout.setHintTextColor(ColorStateList.valueOf(validColor));
}
/**
* Alters the UI to indicate when input is valid or invalid.
* Note: In the hours field, if the hours are equal to 24, the entry can be validated with
* the enter key, otherwise the enter key will switch to the seconds field.
*/
private class TextChangeListener implements TextWatcher {
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
String hoursText = mEditHours.getText() != null ? mEditHours.getText().toString() : "";
String minutesText = mEditMinutes.getText() != null ? mEditMinutes.getText().toString() : "";
String secondsText = mEditSeconds.getText() != null ? mEditSeconds.getText().toString() : "";
if (isInvalidInput(hoursText, minutesText, secondsText)) {
updateDialogForInvalidInput();
return;
}
updateDialogForValidInput();
int hours = 0;
if (!hoursText.isEmpty()) {
hours = Integer.parseInt(hoursText);
}
if (hours == 24) {
mEditHours.setImeOptions(EditorInfo.IME_ACTION_DONE);
mEditHours.setOnEditorActionListener(new ImeDoneListener());
mEditMinutes.setEnabled(false);
mEditSeconds.setEnabled(false);
} else {
mEditHours.setImeOptions(EditorInfo.IME_ACTION_NEXT);
mEditMinutes.setEnabled(true);
mEditMinutes.setImeOptions(EditorInfo.IME_ACTION_NEXT & EditorInfo.IME_ACTION_PREVIOUS);
mEditSeconds.setEnabled(true);
}
mEditHours.setInputType(InputType.TYPE_CLASS_NUMBER);
mInput.restartInput(mEditHours);
}
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
}
}
/**
* Handles completing the new duration from the IME keyboard.
*/
private class ImeDoneListener implements TextView.OnEditorActionListener {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
String inputHoursText = Objects.requireNonNull(mEditHours.getText()).toString();
String inputMinutesText = Objects.requireNonNull(mEditMinutes.getText()).toString();
String inputSecondsText = Objects.requireNonNull(mEditSeconds.getText()).toString();
if (isInvalidInput(inputHoursText, inputMinutesText, inputSecondsText)) {
updateDialogForInvalidInput();
} else {
setNewDuration();
dismiss();
}
return true;
}
return false;
}
}
}

View file

@ -41,14 +41,44 @@ public class TimerViewHolder extends RecyclerView.ViewHolder {
mTimerItem = (TimerItem) view;
mTimerClickHandler = timerClickHandler;
view.findViewById(R.id.reset).setOnClickListener(v -> {
View timerLabel = view.findViewById(R.id.timer_label);
View resetButton = view.findViewById(R.id.reset);
View timerTotalDuration = view.findViewById(R.id.timer_total_duration);
View addTimeButton = view.findViewById(R.id.timer_add_time_button);
View circleContainer = view.findViewById(R.id.circle_container);
View timerTimeText = view.findViewById(R.id.timer_time_text);
View playPauseButton = view.findViewById(R.id.play_pause);
View deleteButton = view.findViewById(R.id.delete_timer);
View.OnClickListener playPauseListener = v -> {
Utils.setVibrationTime(context, 50);
if (getTimer().isPaused() || getTimer().isReset()) {
DataModel.getDataModel().startTimer(getTimer());
} else if (getTimer().isRunning()) {
DataModel.getDataModel().pauseTimer(getTimer());
} else if (getTimer().isExpired() || getTimer().isMissed()) {
DataModel.getDataModel().resetOrDeleteExpiredTimers(R.string.label_deskclock);
}
};
View.OnLongClickListener setNewDurationListener = v -> {
if (!getTimer().isReset()) {
return false;
}
mTimerClickHandler.onDurationClicked(getTimer());
return true;
};
timerLabel.setOnClickListener(v -> mTimerClickHandler.onEditLabelClicked(getTimer()));
resetButton.setOnClickListener(v -> {
DataModel.getDataModel().resetOrDeleteTimer(getTimer(), R.string.label_deskclock);
Utils.setVibrationTime(context, 10);
});
view.findViewById(R.id.timer_add_time_button).setOnClickListener(v -> {
final Timer timer = getTimer();
DataModel.getDataModel().addCustomTimeToTimer(timer);
addTimeButton.setOnClickListener(v -> {
DataModel.getDataModel().addCustomTimeToTimer(getTimer());
Utils.setVibrationTime(context, 10);
Events.sendTimerEvent(R.string.action_add_custom_time_to_timer, R.string.label_deskclock);
@ -61,37 +91,31 @@ public class TimerViewHolder extends RecyclerView.ViewHolder {
}
});
view.findViewById(R.id.timer_add_time_button).setOnLongClickListener(v -> {
addTimeButton.setOnLongClickListener(v -> {
mTimerClickHandler.onEditAddTimeButtonLongClicked(getTimer());
return true;
});
view.findViewById(R.id.timer_label).setOnClickListener(v -> mTimerClickHandler.onEditLabelClicked(getTimer()));
View.OnClickListener mPlayPauseListener = v -> {
Utils.setVibrationTime(context, 50);
final Timer clickedTimer = getTimer();
if (clickedTimer.isPaused() || clickedTimer.isReset()) {
DataModel.getDataModel().startTimer(clickedTimer);
} else if (clickedTimer.isRunning()) {
DataModel.getDataModel().pauseTimer(clickedTimer);
} else if (clickedTimer.isExpired() || clickedTimer.isMissed()) {
DataModel.getDataModel().resetOrDeleteExpiredTimers(R.string.label_deskclock);
}
};
// If we click on the circular container when the phones (only) are in landscape mode,
// indicating a title for the timers is not possible so in this case we click on the time text.
if (!ThemeUtils.isTablet() && ThemeUtils.isLandscape()) {
view.findViewById(R.id.timer_time_text).setOnClickListener(mPlayPauseListener);
} else {
view.findViewById(R.id.circle_container).setOnClickListener(mPlayPauseListener);
view.findViewById(R.id.circle_container).setOnTouchListener(new Utils.CircleTouchListener());
if (timerTotalDuration != null) {
timerTotalDuration.setOnLongClickListener(setNewDurationListener);
}
view.findViewById(R.id.play_pause).setOnClickListener(mPlayPauseListener);
// If we click on the circular container when the phones (only) are in landscape mode,
// indicating a title for the timers is not possible so in this case we click on the
// time to start the timer.
// Long press on the time displays the dialog to set a new timer duration.
if (!ThemeUtils.isTablet() && ThemeUtils.isLandscape()) {
timerTimeText.setOnLongClickListener(setNewDurationListener);
timerTimeText.setOnClickListener(playPauseListener);
} else {
circleContainer.setOnLongClickListener(setNewDurationListener);
circleContainer.setOnClickListener(playPauseListener);
circleContainer.setOnTouchListener(new Utils.CircleTouchListener());
}
view.findViewById(R.id.delete_timer).setOnClickListener(v -> {
playPauseButton.setOnClickListener(playPauseListener);
deleteButton.setOnClickListener(v -> {
Utils.setVibrationTime(context, 10);
if (SettingsDAO.isWarningDisplayedBeforeDeletingTimer(getDefaultSharedPreferences(context))) {

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-License-Identifier: GPL-3.0-only
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingTop="20dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_input_layout_hours"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_hours"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:hint="@string/label_hours"
android:inputType="number"
android:maxLength="2" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_input_layout_minutes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_minutes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:hint="@string/label_minutes"
android:inputType="number"
android:maxLength="2" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_input_layout_seconds"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_seconds"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/label_seconds"
android:inputType="number"
android:maxLength="2" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>

View file

@ -86,6 +86,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:paddingStart="@null"
android:paddingEnd="4dp"
android:paddingVertical="8dp"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:textStyle="bold"
android:textSize="24sp"

View file

@ -445,7 +445,7 @@
<string name="first_launch_important_info_message_for_SDK34">&lt;b&gt;OZNÁMENÍ NA CELÉ OBRAZOVCE;&lt;/b&gt; &lt;br&gt; &lt;br&gt;</string>
<string name="widgets_settings">Widgety</string>
<string name="settings_language_turkish">Turečtina</string>
<string name="timer_button_time_warning_box_title">Zadána neplatná hodnota</string>
<string name="timer_time_warning_box_title">Zadána neplatná hodnota</string>
<string name="contributors">Přispěvatelé</string>
<string name="about_github_link">Zobrazit na GitHubu</string>
<string name="license">Licence open source</string>

View file

@ -645,7 +645,7 @@
<string name="settings_tab_to_display">Tab, der beim Öffnen der App angezeigt wird</string>
<string name="timer_button_time_minutes_warning_box_text">Geben Sie einen Wert zwischen 0 und 60 Minuten ein</string>
<string name="last_tab_used_title">Zuletzt verwendeter Tab</string>
<string name="timer_button_time_warning_box_title">Falscher Wert eingegeben</string>
<string name="timer_time_warning_box_title">Falscher Wert eingegeben</string>
<string name="settings_language_turkish">Türkisch</string>
<string name="reset_settings_title">Einstellungen zurücksetzen</string>
<string name="contributors_dialog_title">Profil ansehen</string>

View file

@ -658,5 +658,5 @@
<string name="toast_message_for_reset">Reinicio completo</string>
<string name="shake_intensity_title">Intensidad de sacudida</string>
<string name="clock_style_spinner">Spinner</string>
<string name="timer_button_time_warning_box_title">Valor incorrecto ingresado</string>
<string name="timer_time_warning_box_title">Valor incorrecto ingresado</string>
</resources>

View file

@ -652,7 +652,7 @@
<string name="analog_widget_configuration_warning">Vidin on seadistatav vaid alates Androidi versioonist 12</string>
<string name="last_tab_used_title">Viimatikasutatud vahekaart</string>
<string name="timer_button_time_minutes_warning_box_text">Sisesta väärtus 0 ja 60 minuti vahemikust</string>
<string name="timer_button_time_warning_box_title">Sisestasid vigase väärtuse</string>
<string name="timer_time_warning_box_title">Sisestasid vigase väärtuse</string>
<string name="settings_tab_to_display">Rakenduse avamisel kuvatav vahekaart</string>
<string name="settings_language_turkish">türgi keel</string>
<string name="reset_settings_title">Lähtesta seadistused</string>

View file

@ -657,7 +657,7 @@
<string name="analog_widget_configuration_warning">Ce widget n\'est configurable qu\'à partir d\'Android 12</string>
<string name="analog_widget_choice_title">Choisissez votre style</string>
<string name="last_tab_used_title">Dernier onglet utilisé</string>
<string name="timer_button_time_warning_box_title">Valeur saisie incorrecte</string>
<string name="timer_time_warning_box_title">Valeur saisie incorrecte</string>
<string name="settings_tab_to_display">Onglet à afficher lors de l\'ouverture de l\'application</string>
<string name="timer_button_time_minutes_warning_box_text">Saisir une valeur comprise entre 0 et 60 minutes</string>
<string name="settings_language_turkish">Turc</string>

View file

@ -655,7 +655,7 @@
<string name="blue_accent_color">Blu</string>
<string name="settings_language_korean">Coreano</string>
<string name="timer_button_time_minutes_warning_box_text">Inserisci un valore tra 0 e 60 minuti</string>
<string name="timer_button_time_warning_box_title">Valore inserito non valido</string>
<string name="timer_time_warning_box_title">Valore inserito non valido</string>
<string name="toast_message_for_reset">Ripristino completato</string>
<string name="settings_tab_to_display">Scheda da visualizzare all\'apertura dell\'applicazione</string>
<string name="last_tab_used_title">Ultima scheda utilizzata</string>

View file

@ -615,7 +615,7 @@
<string name="blue_accent_color">파란색</string>
<string name="toast_message_for_reset">초기화를 완료했습니다.</string>
<string name="timer_button_time_minutes_warning_box_text">0분에서 60분 사이의 시간을 입력할 수 있습니다.</string>
<string name="timer_button_time_warning_box_title">잘못된 값 입력됨</string>
<string name="timer_time_warning_box_title">잘못된 값 입력됨</string>
<string name="sw_long_press_volume_up_action_title">볼륨 위(길게 눌렀을 때)</string>
<string name="analog_widget_configuration_warning">이 위젯은 Android 12 이상에서만 설정할 수 있습니다.</string>
<string name="actions_category_title">동작</string>

View file

@ -645,7 +645,7 @@
<string name="settings_tab_to_display">Tabblad openen bij start</string>
<string name="timer_button_time_minutes_warning_box_text">Voer een waarde in tussen 0 en 60 minuten</string>
<string name="last_tab_used_title">Laatst gebruikt</string>
<string name="timer_button_time_warning_box_title">Onjuiste waarde ingevoerd</string>
<string name="timer_time_warning_box_title">Onjuiste waarde ingevoerd</string>
<string name="settings_language_turkish">Turks</string>
<string name="contributors_dialog_title">Profiel bekijken</string>
<string name="reset_settings_title">Instellingen opnieuw instellen</string>

View file

@ -661,7 +661,7 @@
<string name="settings_tab_to_display">Karta wyświetlana podczas otwierania aplikacji</string>
<string name="last_tab_used_title">Ostatnio używana karta</string>
<string name="timer_button_time_minutes_warning_box_text">Wprowadź wartość od 0 do 60 minut</string>
<string name="timer_button_time_warning_box_title">Wprowadzono nieprawidłową wartość</string>
<string name="timer_time_warning_box_title">Wprowadzono nieprawidłową wartość</string>
<string name="settings_language_turkish">Turecki</string>
<string name="reset_settings_title">Zresetuj ustawienia</string>
<string name="reset_settings_message">Czy na pewno chcesz zresetować wszystkie ustawienia aplikacji?\n\nUwaga: dzwonki niestandardowe nie zostaną zresetowane.</string>

View file

@ -672,7 +672,7 @@
<string name="toast_message_for_reset">Сброс выполнен</string>
<string name="settings_language_korean">Корейский</string>
<string name="timer_button_time_minutes_warning_box_text">Введите значение между 0 и 60 минутами</string>
<string name="timer_button_time_warning_box_title">Неверное значение введено</string>
<string name="timer_time_warning_box_title">Неверное значение введено</string>
<string name="settings_language_russian">Русский</string>
<string name="display_date_title">Показывать дату на виджете</string>
<string name="analog_widget_choice_title">Выберите ваш стиль</string>

View file

@ -617,7 +617,7 @@
<string name="display_warning_before_deleting_timer_title">Прикажи упозорење пре брисања тајмера</string>
<string name="warning_dialog_title">Обриши тајмер</string>
<string name="settings_language_turkish">Турски</string>
<string name="timer_button_time_warning_box_title">Унета је некоректна вредност</string>
<string name="timer_time_warning_box_title">Унета је некоректна вредност</string>
<string name="about_translate_link">Преведите на Codeberg-у</string>
<string name="translate_dialog_message">Посетите страницу Codeberg:\n\n%s</string>
<string name="clock_style_analog_material">Аналогни (Материјал)</string>

View file

@ -398,7 +398,7 @@
<string name="sw_start_pause_action">Başlat / Duraklat</string>
<string name="timer_label_box_title">Zamanlayıcı etiketi</string>
<string name="timer_button_time_box_title">Zamanlayıcıya eklenecek dakika</string>
<string name="timer_button_time_warning_box_title">Geçersiz değer girildi</string>
<string name="timer_time_warning_box_title">Geçersiz değer girildi</string>
<string name="purple_accent_color">Mor</string>
<string name="auto_silence_20_seconds">20 saniye</string>
<string name="black_accent_color">Siyah</string>

View file

@ -672,7 +672,7 @@
<string name="analog_widget_without_second_hand_title">Без секундної стрілки</string>
<string name="last_tab_used_title">Остання використана вкладка</string>
<string name="settings_tab_to_display">Вкладка, на якій відкриватиметься додаток</string>
<string name="timer_button_time_warning_box_title">Введено неправильне значення</string>
<string name="timer_time_warning_box_title">Введено неправильне значення</string>
<string name="timer_button_time_minutes_warning_box_text">Введіть значення від 0 до 60 хвилин</string>
<string name="settings_language_turkish">Турецька</string>
<string name="reset_settings_message">Ви дійсно хочете скинути всі налаштування додатка?\n\nЗауважте, що користувацькі мелодії не скидаються.</string>

View file

@ -633,7 +633,7 @@
<string name="analog_widget_configuration_warning">此微件仅可从 Android 12 配置</string>
<string name="analog_widget_choice_title">选择样式</string>
<string name="last_tab_used_title">上次使用的标签页‌</string>
<string name="timer_button_time_warning_box_title">输入的值不正确</string>
<string name="timer_time_warning_box_title">输入的值不正确</string>
<string name="settings_tab_to_display">打开应用时显示的标签页‌</string>
<string name="timer_button_time_minutes_warning_box_text">请输入介于 0 和 60 分钟之间的值</string>
<string name="settings_language_turkish">土耳其语</string>

View file

@ -465,7 +465,7 @@
<string name="alarm_settings_general_summary">您可展開鬧鐘面板並各別設定</string>
<string name="alarm_display_customization_title">自訂鬧鐘顯示</string>
<string name="timer_label_box_title">計時器名稱</string>
<string name="timer_button_time_warning_box_title">數值無效</string>
<string name="timer_time_warning_box_title">數值無效</string>
<string name="warning_dialog_title">刪除計時器</string>
<string name="occasional_alarm_deleted">已刪除臨時鬧鐘「<xliff:g example="14:20" id="alarm_time">%s</xliff:g></string>
<string name="first_launch_main_feature_title">主要功能</string>

View file

@ -42,6 +42,7 @@
<string name="action_reset">Reset</string>
<string name="action_show">Show</string>
<string name="action_add_custom_time_to_timer">Add Custom Time To Timer</string>
<string name="action_set_new_timer_duration">Set New Timer Duration</string>
<string name="action_lap">Lap</string>
<string name="action_enable">Enable</string>
<string name="action_disable">Disable</string>

View file

@ -37,6 +37,8 @@
<string name="duplicate">Duplicate</string>
<!-- Label "Warning" -->
<string name="warning">Warning</string>
<!-- Label "Hours" -->
<string name="label_hours">Hours</string>
<!-- Label "Minutes" -->
<string name="label_minutes">Minutes</string>
<!-- Label "Seconds" -->
@ -679,14 +681,22 @@
<!-- timer strings -->
<!-- Title of dialog box for editing timer label -->
<string name="timer_label_box_title">Timer label</string>
<!-- Title of dialog box to set a new duration to an existing timer. -->
<string name="timer_time_box_title">New timer duration</string>
<!-- Text of dialog box to edit hours to set a new duration to an existing timer. -->
<string name="timer_hours_warning_box_text">Enter a value between 0 and 24 hours</string>
<!-- Text of dialog box to edit minutes to set a new duration to an existing timer. -->
<string name="timer_minutes_warning_box_text">Enter a value between 0 and 59 minutes</string>
<!-- Text of dialog box to edit seconds to set a new duration to an existing timer. -->
<string name="timer_seconds_warning_box_text">Enter a value between 0 and 59 seconds</string>
<!-- Title of dialog box to edit button for adding time to timer. -->
<string name="timer_button_time_box_title">Minutes to add to the timer</string>
<!-- Text of dialog box to edit minutes for adding time to timer. -->
<string name="timer_button_time_minutes_warning_box_text">Enter a value between 0 and 60 minutes</string>
<!-- Text of dialog box to edit seconds for adding time to timer. -->
<string name="timer_button_time_seconds_warning_box_text">Enter a value between 0 and 59 seconds</string>
<!-- Title of dialog box to edit button for adding time to timer if the entered value is incorrect. -->
<string name="timer_button_time_warning_box_title">Incorrect value entered</string>
<!-- Title of dialog box to edit timers if an entered value is incorrect. -->
<string name="timer_time_warning_box_title">Incorrect value entered</string>
<!-- Describes the purpose of the button to add a new timer -->
<string name="timer_add_timer">Add Timer</string>
<!-- Describes the purpose of the button to begin or continue running a timer -->