mirror of
https://github.com/BlackyHawky/Clock.git
synced 2025-07-03 07:23:21 +00:00
Restore old layout for timers - Fix #265
- Add an icon in the bottom left corner to edit the new timer duration (except for portrait phones with multiple timers);
This commit is contained in:
parent
fc8d5c6dcb
commit
8d52e50ac9
13 changed files with 530 additions and 209 deletions
|
@ -15,8 +15,6 @@ import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
@ -28,6 +26,7 @@ import com.best.deskclock.data.DataModel;
|
||||||
import com.best.deskclock.data.Timer;
|
import com.best.deskclock.data.Timer;
|
||||||
import com.best.deskclock.utils.ThemeUtils;
|
import com.best.deskclock.utils.ThemeUtils;
|
||||||
|
|
||||||
|
import com.best.deskclock.widget.CircleButtonsLayout;
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
import com.google.android.material.color.MaterialColors;
|
import com.google.android.material.color.MaterialColors;
|
||||||
|
|
||||||
|
@ -38,10 +37,12 @@ import java.util.Locale;
|
||||||
*/
|
*/
|
||||||
public class TimerItem extends ConstraintLayout {
|
public class TimerItem extends ConstraintLayout {
|
||||||
|
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The container of TimerCircleView and TimerTextController
|
* The container of TimerCircleView and TimerTextController
|
||||||
*/
|
*/
|
||||||
private FrameLayout mCircleContainer;
|
private CircleButtonsLayout mCircleContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats and displays the text in the timer.
|
* Formats and displays the text in the timer.
|
||||||
|
@ -59,6 +60,9 @@ public class TimerItem extends ConstraintLayout {
|
||||||
/** A button that resets the timer. */
|
/** A button that resets the timer. */
|
||||||
private ImageButton mResetButton;
|
private ImageButton mResetButton;
|
||||||
|
|
||||||
|
/** A button that edits a new duration to the timer. */
|
||||||
|
private ImageButton mTimerEditNewDurationButton;
|
||||||
|
|
||||||
/** A button that adds time to the timer. */
|
/** A button that adds time to the timer. */
|
||||||
private MaterialButton mAddTimeButton;
|
private MaterialButton mAddTimeButton;
|
||||||
|
|
||||||
|
@ -82,7 +86,6 @@ public class TimerItem extends ConstraintLayout {
|
||||||
|
|
||||||
private boolean mIsTablet;
|
private boolean mIsTablet;
|
||||||
private boolean mIsPortrait;
|
private boolean mIsPortrait;
|
||||||
private boolean mIsLandscape;
|
|
||||||
|
|
||||||
public TimerItem(Context context) {
|
public TimerItem(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -96,29 +99,22 @@ public class TimerItem extends ConstraintLayout {
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
|
||||||
|
mContext = getContext();
|
||||||
mIsTablet = ThemeUtils.isTablet();
|
mIsTablet = ThemeUtils.isTablet();
|
||||||
mIsPortrait = ThemeUtils.isPortrait();
|
mIsPortrait = ThemeUtils.isPortrait();
|
||||||
mIsLandscape = ThemeUtils.isLandscape();
|
|
||||||
|
|
||||||
// To avoid creating a layout specifically for tablets, adjust the layout width and height here
|
setBackground(ThemeUtils.cardBackground(mContext));
|
||||||
if (mIsTablet && mIsLandscape) {
|
|
||||||
final ViewGroup.LayoutParams params = getLayoutParams();
|
|
||||||
params.width = LayoutParams.MATCH_PARENT;
|
|
||||||
params.height = LayoutParams.WRAP_CONTENT;
|
|
||||||
setLayoutParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
setBackground(ThemeUtils.cardBackground(getContext()));
|
|
||||||
|
|
||||||
mLabelView = findViewById(R.id.timer_label);
|
mLabelView = findViewById(R.id.timer_label);
|
||||||
mResetButton = findViewById(R.id.reset);
|
mResetButton = findViewById(R.id.reset);
|
||||||
mAddTimeButton = findViewById(R.id.timer_add_time_button);
|
mAddTimeButton = findViewById(R.id.timer_add_time_button);
|
||||||
mPlayPauseButton = findViewById(R.id.play_pause);
|
mPlayPauseButton = findViewById(R.id.play_pause);
|
||||||
mCircleContainer = findViewById(R.id.circle_container);
|
mCircleContainer = findViewById(R.id.circle_container);
|
||||||
mCircleView = mCircleContainer.findViewById(R.id.timer_time);
|
mCircleView = findViewById(R.id.timer_time);
|
||||||
// Displays the remaining time or time since expiration. Timer text serves as a virtual start/stop button.
|
// Displays the remaining time or time since expiration.
|
||||||
mTimerText = mCircleContainer.findViewById(R.id.timer_time_text);
|
// Timer text serves as a virtual start/stop button.
|
||||||
final int colorAccent = MaterialColors.getColor(getContext(),
|
mTimerText = findViewById(R.id.timer_time_text);
|
||||||
|
final int colorAccent = MaterialColors.getColor(mContext,
|
||||||
com.google.android.material.R.attr.colorPrimary, Color.BLACK);
|
com.google.android.material.R.attr.colorPrimary, Color.BLACK);
|
||||||
final int textColorPrimary = mTimerText.getCurrentTextColor();
|
final int textColorPrimary = mTimerText.getCurrentTextColor();
|
||||||
final ColorStateList timeTextColor = new ColorStateList(
|
final ColorStateList timeTextColor = new ColorStateList(
|
||||||
|
@ -126,32 +122,12 @@ public class TimerItem extends ConstraintLayout {
|
||||||
new int[]{textColorPrimary, colorAccent});
|
new int[]{textColorPrimary, colorAccent});
|
||||||
mTimerText.setTextColor(timeTextColor);
|
mTimerText.setTextColor(timeTextColor);
|
||||||
mTimerTextController = new TimerTextController(mTimerText);
|
mTimerTextController = new TimerTextController(mTimerText);
|
||||||
|
mTimerEditNewDurationButton = findViewById(R.id.timer_edit_new_duration_button);
|
||||||
// Necessary to avoid the null pointer exception,
|
// Needed to avoid the null pointer exception, as only phones in portrait mode with
|
||||||
// as only the timer_item layout for portrait mode has these attributes
|
// multiple timers have this text
|
||||||
if (isPortraitPhoneWithMultipleTimers()) {
|
if (isPortraitPhoneWithMultipleTimers()) {
|
||||||
mTimerTotalDurationText = findViewById(R.id.timer_total_duration);
|
mTimerTotalDurationText = findViewById(R.id.timer_total_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The size of the Play/Pause and add time buttons are reduced for phones in landscape mode
|
|
||||||
// due to the size of the timers unlike tablets
|
|
||||||
if (!mIsTablet && mIsLandscape) {
|
|
||||||
mAddTimeButton.setIncludeFontPadding(false);
|
|
||||||
mAddTimeButton.setMinHeight(0);
|
|
||||||
mAddTimeButton.setMinimumHeight(0);
|
|
||||||
mAddTimeButton.setMinWidth(0);
|
|
||||||
mAddTimeButton.setMinimumWidth(0);
|
|
||||||
mAddTimeButton.setPadding(ThemeUtils.convertDpToPixels(10, getContext()), mAddTimeButton.getPaddingTop(),
|
|
||||||
ThemeUtils.convertDpToPixels(10, getContext()), mAddTimeButton.getPaddingBottom());
|
|
||||||
|
|
||||||
mPlayPauseButton.setIncludeFontPadding(false);
|
|
||||||
mPlayPauseButton.setMinHeight(0);
|
|
||||||
mPlayPauseButton.setMinimumHeight(0);
|
|
||||||
mPlayPauseButton.setMinWidth(0);
|
|
||||||
mPlayPauseButton.setMinimumWidth(0);
|
|
||||||
mPlayPauseButton.setPadding(ThemeUtils.convertDpToPixels(20, getContext()), mPlayPauseButton.getPaddingTop(),
|
|
||||||
ThemeUtils.convertDpToPixels(20, getContext()), mPlayPauseButton.getPaddingBottom());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,8 +139,7 @@ public class TimerItem extends ConstraintLayout {
|
||||||
mTimerTextController.setTimeString(timer.getRemainingTime());
|
mTimerTextController.setTimeString(timer.getRemainingTime());
|
||||||
|
|
||||||
if (mCircleView != null) {
|
if (mCircleView != null) {
|
||||||
final boolean hideCircle = ((timer.isExpired() || timer.isMissed()) && blinkOff)
|
final boolean hideCircle = ((timer.isExpired() || timer.isMissed()) && blinkOff);
|
||||||
|| (!mIsTablet && mIsLandscape);
|
|
||||||
|
|
||||||
mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE);
|
mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE);
|
||||||
|
|
||||||
|
@ -187,6 +162,7 @@ public class TimerItem extends ConstraintLayout {
|
||||||
// Initialize the time.
|
// Initialize the time.
|
||||||
mTimerTextController.setTimeString(timer.getRemainingTime());
|
mTimerTextController.setTimeString(timer.getRemainingTime());
|
||||||
|
|
||||||
|
// Initialize text for timer total duration
|
||||||
if (isPortraitPhoneWithMultipleTimers() && mTimerTotalDurationText != null) {
|
if (isPortraitPhoneWithMultipleTimers() && mTimerTotalDurationText != null) {
|
||||||
mTimerTotalDurationText.setText(timer.getTotalDuration());
|
mTimerTotalDurationText.setText(timer.getTotalDuration());
|
||||||
}
|
}
|
||||||
|
@ -203,54 +179,86 @@ public class TimerItem extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the circle
|
// Initialize the circle
|
||||||
// (the circle is hidden for landscape phones because there is not enough space)
|
|
||||||
if (mCircleView != null) {
|
if (mCircleView != null) {
|
||||||
final boolean hideCircle = !mIsTablet && mIsLandscape;
|
mCircleView.update(timer);
|
||||||
|
|
||||||
mCircleView.setVisibility(hideCircle ? INVISIBLE : VISIBLE);
|
|
||||||
|
|
||||||
if (!hideCircle) {
|
|
||||||
mCircleView.update(timer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the alpha value of the time text color
|
||||||
mTimerText.setAlpha(1f);
|
mTimerText.setAlpha(1f);
|
||||||
|
|
||||||
// Initialize the time value to add to timer in the "timer_add_time_button"
|
// Initialize the time value to add to timer in the "Add time" button
|
||||||
String buttonTime = timer.getButtonTime();
|
String buttonTime = timer.getButtonTime();
|
||||||
long totalSeconds = Long.parseLong(buttonTime);
|
long totalSeconds = Long.parseLong(buttonTime);
|
||||||
long buttonTimeMinutes = (totalSeconds) / 60;
|
long buttonTimeMinutes = (totalSeconds) / 60;
|
||||||
long buttonTimeSeconds = totalSeconds % 60;
|
long buttonTimeSeconds = totalSeconds % 60;
|
||||||
|
|
||||||
String buttonTimeFormatted = String.format(
|
String buttonTimeFormatted = String.format(
|
||||||
Locale.getDefault(),
|
Locale.getDefault(),
|
||||||
buttonTimeMinutes < 10 ? "%d:%02d" : "%02d:%02d",
|
buttonTimeMinutes < 10 ? "%d:%02d" : "%02d:%02d",
|
||||||
buttonTimeMinutes,
|
buttonTimeMinutes,
|
||||||
buttonTimeSeconds);
|
buttonTimeSeconds);
|
||||||
|
|
||||||
mAddTimeButton.setText(getContext().getString(R.string.timer_add_custom_time, buttonTimeFormatted));
|
mAddTimeButton.setText(mContext.getString(R.string.timer_add_custom_time, buttonTimeFormatted));
|
||||||
|
|
||||||
String buttonContentDescription = buttonTimeSeconds == 0
|
String buttonContentDescription = buttonTimeSeconds == 0
|
||||||
? getContext().getString(R.string.timer_add_custom_time_description, String.valueOf(buttonTimeMinutes))
|
? mContext.getString(R.string.timer_add_custom_time_description, String.valueOf(buttonTimeMinutes))
|
||||||
: getContext().getString(R.string.timer_add_custom_time_with_seconds_description,
|
: mContext.getString(R.string.timer_add_custom_time_with_seconds_description,
|
||||||
String.valueOf(buttonTimeMinutes),
|
String.valueOf(buttonTimeMinutes),
|
||||||
String.valueOf(buttonTimeSeconds));
|
String.valueOf(buttonTimeSeconds));
|
||||||
mAddTimeButton.setContentDescription(buttonContentDescription);
|
mAddTimeButton.setContentDescription(buttonContentDescription);
|
||||||
|
|
||||||
|
// For tablets in portrait mode with single timer, adjust the size of the "Add time" and
|
||||||
|
// "Play/Pause" buttons
|
||||||
|
final ConstraintLayout.LayoutParams addTimeButtonParams =
|
||||||
|
(ConstraintLayout.LayoutParams) mAddTimeButton.getLayoutParams();
|
||||||
|
final ConstraintLayout.LayoutParams playPauseButtonParams =
|
||||||
|
(ConstraintLayout.LayoutParams) mPlayPauseButton.getLayoutParams();
|
||||||
|
|
||||||
|
if (mIsTablet && mIsPortrait && DataModel.getDataModel().getTimers().size() == 1) {
|
||||||
|
addTimeButtonParams.matchConstraintMaxHeight = ThemeUtils.convertDpToPixels(200, mContext);
|
||||||
|
playPauseButtonParams.matchConstraintMaxHeight = ThemeUtils.convertDpToPixels(200, mContext);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize some potentially expensive areas of the user interface only on state changes.
|
// Initialize some potentially expensive areas of the user interface only on state changes.
|
||||||
if (timer.getState() != mLastState) {
|
if (timer.getState() != mLastState) {
|
||||||
final Context context = getContext();
|
final String resetDesc = mContext.getString(R.string.reset);
|
||||||
final String resetDesc = context.getString(R.string.reset);
|
|
||||||
mResetButton.setVisibility(VISIBLE);
|
mResetButton.setVisibility(VISIBLE);
|
||||||
mResetButton.setContentDescription(resetDesc);
|
mResetButton.setContentDescription(resetDesc);
|
||||||
mAddTimeButton.setVisibility(VISIBLE);
|
mAddTimeButton.setVisibility(VISIBLE);
|
||||||
mLastState = timer.getState();
|
mLastState = timer.getState();
|
||||||
|
|
||||||
// If the timer is reset, add a top margin so that the "Play" button is not stuck to the "Delete" button.
|
// For phones in portrait mode, when there are multiple timers and they are reset,
|
||||||
|
// adjust the constraints, margins and paddings of the "Play/Pause" button to have
|
||||||
|
// a suitable reduced view
|
||||||
if (isPortraitPhoneWithMultipleTimers()) {
|
if (isPortraitPhoneWithMultipleTimers()) {
|
||||||
final ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) mPlayPauseButton.getLayoutParams();
|
if (mLastState.equals(Timer.State.RESET)) {
|
||||||
params.topMargin = ThemeUtils.convertDpToPixels(timer.getState().equals(Timer.State.RESET) ? 10 : 0, context);
|
playPauseButtonParams.width = LayoutParams.WRAP_CONTENT;
|
||||||
mPlayPauseButton.setLayoutParams(params);
|
|
||||||
|
playPauseButtonParams.startToStart = ConstraintLayout.LayoutParams.UNSET;
|
||||||
|
playPauseButtonParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||||
|
playPauseButtonParams.topToBottom = mLabelView.getId();
|
||||||
|
playPauseButtonParams.bottomToBottom = ConstraintLayout.LayoutParams.UNSET;
|
||||||
|
|
||||||
|
playPauseButtonParams.rightMargin = ThemeUtils.convertDpToPixels(12, mContext);
|
||||||
|
playPauseButtonParams.topMargin = ThemeUtils.convertDpToPixels(10, mContext);
|
||||||
|
|
||||||
|
mPlayPauseButton.setPadding(0, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
int playPauseButtonPadding = ThemeUtils.convertDpToPixels(24, mContext);
|
||||||
|
|
||||||
|
playPauseButtonParams.width = 0;
|
||||||
|
|
||||||
|
playPauseButtonParams.startToStart = mAddTimeButton.getId();
|
||||||
|
playPauseButtonParams.endToEnd = mAddTimeButton.getId();
|
||||||
|
playPauseButtonParams.topToBottom = ConstraintLayout.LayoutParams.UNSET;
|
||||||
|
playPauseButtonParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||||
|
|
||||||
|
playPauseButtonParams.rightMargin = 0;
|
||||||
|
playPauseButtonParams.topMargin = 0;
|
||||||
|
|
||||||
|
mPlayPauseButton.setPadding(playPauseButtonPadding, playPauseButtonPadding,
|
||||||
|
playPauseButtonPadding, playPauseButtonPadding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mLastState) {
|
switch (mLastState) {
|
||||||
|
@ -258,7 +266,11 @@ public class TimerItem extends ConstraintLayout {
|
||||||
mResetButton.setVisibility(GONE);
|
mResetButton.setVisibility(GONE);
|
||||||
mResetButton.setContentDescription(null);
|
mResetButton.setContentDescription(null);
|
||||||
mAddTimeButton.setVisibility(INVISIBLE);
|
mAddTimeButton.setVisibility(INVISIBLE);
|
||||||
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(context, R.drawable.ic_fab_play));
|
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.ic_fab_play));
|
||||||
|
|
||||||
|
if (isTabletOrLandscapePhone() || isPhoneWithSingleTimer()) {
|
||||||
|
mTimerEditNewDurationButton.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPortraitPhoneWithMultipleTimers()) {
|
if (isPortraitPhoneWithMultipleTimers()) {
|
||||||
mCircleContainer.setVisibility(GONE);
|
mCircleContainer.setVisibility(GONE);
|
||||||
|
@ -267,7 +279,12 @@ public class TimerItem extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
case PAUSED -> {
|
case PAUSED -> {
|
||||||
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(context, R.drawable.ic_fab_play));
|
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.ic_fab_play));
|
||||||
|
|
||||||
|
if (isTabletOrLandscapePhone() || isPhoneWithSingleTimer()) {
|
||||||
|
mTimerEditNewDurationButton.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPortraitPhoneWithMultipleTimers()) {
|
if (isPortraitPhoneWithMultipleTimers()) {
|
||||||
mCircleContainer.setVisibility(VISIBLE);
|
mCircleContainer.setVisibility(VISIBLE);
|
||||||
mTimerTotalDurationText.setVisibility(GONE);
|
mTimerTotalDurationText.setVisibility(GONE);
|
||||||
|
@ -275,7 +292,11 @@ public class TimerItem extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
case RUNNING -> {
|
case RUNNING -> {
|
||||||
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(context, R.drawable.ic_fab_pause));
|
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.ic_fab_pause));
|
||||||
|
|
||||||
|
if (isTabletOrLandscapePhone() || isPhoneWithSingleTimer()) {
|
||||||
|
mTimerEditNewDurationButton.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPortraitPhoneWithMultipleTimers()) {
|
if (isPortraitPhoneWithMultipleTimers()) {
|
||||||
mCircleContainer.setVisibility(VISIBLE);
|
mCircleContainer.setVisibility(VISIBLE);
|
||||||
|
@ -284,14 +305,38 @@ public class TimerItem extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
case EXPIRED, MISSED -> {
|
case EXPIRED, MISSED -> {
|
||||||
|
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(mContext, R.drawable.ic_fab_stop));
|
||||||
|
|
||||||
|
if (isTabletOrLandscapePhone() || isPhoneWithSingleTimer()) {
|
||||||
|
mTimerEditNewDurationButton.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
mResetButton.setVisibility(GONE);
|
mResetButton.setVisibility(GONE);
|
||||||
mPlayPauseButton.setIcon(AppCompatResources.getDrawable(context, R.drawable.ic_fab_stop));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the device is a phone in portrait mode with multiple timers displayed.
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
private boolean isPortraitPhoneWithMultipleTimers() {
|
private boolean isPortraitPhoneWithMultipleTimers() {
|
||||||
return !mIsTablet && mIsPortrait && DataModel.getDataModel().getTimers().size() > 1;
|
return !mIsTablet && mIsPortrait && DataModel.getDataModel().getTimers().size() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the device is a tablet or phone in landscape mode.
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
private boolean isTabletOrLandscapePhone() {
|
||||||
|
return (mIsTablet || !mIsPortrait);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the device is a phone with a single timer displayed.
|
||||||
|
*/
|
||||||
|
private boolean isPhoneWithSingleTimer() {
|
||||||
|
return !mIsTablet && DataModel.getDataModel().getTimers().size() == 1;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -44,6 +44,7 @@ public class TimerViewHolder extends RecyclerView.ViewHolder {
|
||||||
View timerLabel = view.findViewById(R.id.timer_label);
|
View timerLabel = view.findViewById(R.id.timer_label);
|
||||||
View resetButton = view.findViewById(R.id.reset);
|
View resetButton = view.findViewById(R.id.reset);
|
||||||
View timerTotalDuration = view.findViewById(R.id.timer_total_duration);
|
View timerTotalDuration = view.findViewById(R.id.timer_total_duration);
|
||||||
|
View timerEditNewDurationButton = view.findViewById(R.id.timer_edit_new_duration_button);
|
||||||
View addTimeButton = view.findViewById(R.id.timer_add_time_button);
|
View addTimeButton = view.findViewById(R.id.timer_add_time_button);
|
||||||
View circleContainer = view.findViewById(R.id.circle_container);
|
View circleContainer = view.findViewById(R.id.circle_container);
|
||||||
View timerTimeText = view.findViewById(R.id.timer_time_text);
|
View timerTimeText = view.findViewById(R.id.timer_time_text);
|
||||||
|
@ -61,15 +62,6 @@ public class TimerViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
View.OnLongClickListener setNewDurationListener = v -> {
|
|
||||||
if (!getTimer().isReset()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mTimerClickHandler.onDurationClicked(getTimer());
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
timerLabel.setOnClickListener(v -> mTimerClickHandler.onEditLabelClicked(getTimer()));
|
timerLabel.setOnClickListener(v -> mTimerClickHandler.onEditLabelClicked(getTimer()));
|
||||||
|
|
||||||
resetButton.setOnClickListener(v -> {
|
resetButton.setOnClickListener(v -> {
|
||||||
|
@ -96,23 +88,37 @@ public class TimerViewHolder extends RecyclerView.ViewHolder {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only possible for portrait mode phones with multiple timers
|
||||||
if (timerTotalDuration != null) {
|
if (timerTotalDuration != null) {
|
||||||
timerTotalDuration.setOnLongClickListener(setNewDurationListener);
|
timerTotalDuration.setOnClickListener(v -> {
|
||||||
|
if (!getTimer().isReset()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mTimerClickHandler.onDurationClicked(getTimer());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we click on the circular container when the phones (only) are in landscape mode,
|
// Only possible for tablets, landscape phones or when there is only one timer
|
||||||
// indicating a title for the timers is not possible so in this case we click on the
|
if (timerEditNewDurationButton != null) {
|
||||||
// time to start the timer.
|
timerEditNewDurationButton.setOnClickListener(v -> {
|
||||||
// Long press on the time displays the dialog to set a new timer duration.
|
if (!getTimer().isReset()) {
|
||||||
if (!ThemeUtils.isTablet() && ThemeUtils.isLandscape()) {
|
return;
|
||||||
timerTimeText.setOnLongClickListener(setNewDurationListener);
|
}
|
||||||
timerTimeText.setOnClickListener(playPauseListener);
|
|
||||||
} else {
|
mTimerClickHandler.onDurationClicked(getTimer());
|
||||||
circleContainer.setOnLongClickListener(setNewDurationListener);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (circleContainer != null) {
|
||||||
circleContainer.setOnClickListener(playPauseListener);
|
circleContainer.setOnClickListener(playPauseListener);
|
||||||
circleContainer.setOnTouchListener(new Utils.CircleTouchListener());
|
circleContainer.setOnTouchListener(new Utils.CircleTouchListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ThemeUtils.isTablet() && ThemeUtils.isLandscape()) {
|
||||||
|
timerTimeText.setOnClickListener(playPauseListener);
|
||||||
|
}
|
||||||
|
|
||||||
playPauseButton.setOnClickListener(playPauseListener);
|
playPauseButton.setOnClickListener(playPauseListener);
|
||||||
|
|
||||||
deleteButton.setOnClickListener(v -> {
|
deleteButton.setOnClickListener(v -> {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Android Open Source Project
|
||||||
|
* modified
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.best.deskclock.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import com.best.deskclock.R;
|
||||||
|
import com.best.deskclock.utils.ThemeUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class adjusts the location of the reset button.
|
||||||
|
*/
|
||||||
|
public class CircleButtonsLayout extends FrameLayout {
|
||||||
|
|
||||||
|
private final float mDiamOffset;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public CircleButtonsLayout(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleButtonsLayout(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
final float strokeSize = ThemeUtils.convertDpToPixels(6, context);
|
||||||
|
mDiamOffset = strokeSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
// We must call onMeasure both before and after re-measuring our views because the circle
|
||||||
|
// may not always be drawn here yet. The first onMeasure will force the circle to be drawn,
|
||||||
|
// and the second will force our re-measurements to take effect.
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
remeasureViews();
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void remeasureViews() {
|
||||||
|
View mCircleView = findViewById(R.id.timer_time);
|
||||||
|
View mResetAddButton = findViewById(R.id.reset);
|
||||||
|
|
||||||
|
final int frameWidth = mCircleView.getMeasuredWidth();
|
||||||
|
final int frameHeight = mCircleView.getMeasuredHeight();
|
||||||
|
final int minBound = Math.min(frameWidth, frameHeight);
|
||||||
|
final int circleDiam = (int) (minBound - mDiamOffset);
|
||||||
|
|
||||||
|
if (mResetAddButton != null) {
|
||||||
|
final MarginLayoutParams resetParams = (MarginLayoutParams) mResetAddButton
|
||||||
|
.getLayoutParams();
|
||||||
|
resetParams.bottomMargin = circleDiam / 8;
|
||||||
|
if (minBound == frameWidth) {
|
||||||
|
resetParams.bottomMargin += (frameHeight - frameWidth) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/res/drawable/ic_edit.xml
Normal file
16
app/src/main/res/drawable/ic_edit.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
modified
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M619 341l29 28-57-57 28 29Zm141-85-56-56 56 56ZM160 840h97q16 0 30.5-6T313 817L817 313q12-12 17.5-26.5T840 256q0-15-5.5-30T817 200l-55-56q-11-12-26-18t-31-6-30.5 6T648 143L143 647q-11 11-17 25.5T120 703v97q0 17 11.5 28.5T160 840Zm40-80V703L591 312l57 57L257 760H200Z" />
|
||||||
|
</vector>
|
|
@ -5,14 +5,16 @@
|
||||||
modified
|
modified
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!-- This TimerItem excludes the circle because not enough space exists. -->
|
||||||
<com.best.deskclock.timer.TimerItem
|
<com.best.deskclock.timer.TimerItem
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginVertical="4dp"
|
android:layout_marginVertical="4dp"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
tools:background="@drawable/card_background_for_preview">
|
tools:background="@drawable/card_background_for_preview">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
@ -32,7 +34,7 @@
|
||||||
app:drawableStartCompat="@drawable/ic_label"
|
app:drawableStartCompat="@drawable/ic_label"
|
||||||
app:drawableTint="?attr/colorOnSurfaceVariant"
|
app:drawableTint="?attr/colorOnSurfaceVariant"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/delete_timer"
|
app:layout_constraintEnd_toStartOf="@+id/delete_timer"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
|
@ -49,76 +51,87 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<!-- Show circle for tablets only. See TimerItem.java -->
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
<FrameLayout
|
android:id="@+id/time_container"
|
||||||
android:id="@+id/circle_container"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="12dp"
|
android:orientation="vertical"
|
||||||
android:layout_marginEnd="12dp"
|
android:gravity="center_vertical|center_horizontal"
|
||||||
android:focusable="true"
|
app:layout_constraintWidth_min="120dp"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/timer_add_time_button"
|
||||||
app:layout_constraintTop_toBottomOf="@id/timer_label"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@id/play_pause">
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
<com.best.deskclock.timer.TimerCircleView
|
|
||||||
android:id="@+id/timer_time"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.best.deskclock.widget.AutoSizingTextView
|
<com.best.deskclock.widget.AutoSizingTextView
|
||||||
android:id="@+id/timer_time_text"
|
android:id="@+id/timer_time_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="24dp"
|
|
||||||
android:includeFontPadding="false"
|
android:includeFontPadding="false"
|
||||||
android:textSize="@dimen/timer_time_text_size"
|
android:textSize="40sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:text="01:23" />
|
tools:text="01:23" />
|
||||||
|
|
||||||
</FrameLayout>
|
<ImageButton
|
||||||
|
android:id="@+id/reset"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_reset"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/reset"
|
||||||
|
app:tint="?attr/colorPrimary"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/timer_time_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
<!-- Buttons size is set programmatically in the TimerItem.java file. -->
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/timer_add_time_button"
|
<ImageButton
|
||||||
|
android:id="@+id/timer_edit_new_duration_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_marginBottom="5dp"
|
android:layout_marginBottom="7dp"
|
||||||
android:textStyle="bold"
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:src="@drawable/ic_edit"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
tools:text="+ 1:00" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/reset"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_reset"
|
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="@string/reset"
|
android:contentDescription="@null"
|
||||||
app:tint="?attr/colorPrimary"
|
app:tint="?android:attr/textColor"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/timer_add_time_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/play_pause"
|
app:layout_constraintBottom_toTopOf="@+id/play_pause"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/play_pause" />
|
tools:text="+ 1:00" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/play_pause"
|
android:id="@+id/play_pause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="12dp"
|
android:padding="12dp"
|
||||||
android:layout_marginBottom="5dp"
|
|
||||||
android:contentDescription="@string/timer_start"
|
android:contentDescription="@string/timer_start"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintStart_toStartOf="@+id/timer_add_time_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/timer_add_time_button"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:icon="@drawable/ic_fab_play" />
|
tools:icon="@drawable/ic_fab_play" />
|
||||||
|
|
||||||
|
|
143
app/src/main/res/layout-sw600dp-land/timer_item.xml
Normal file
143
app/src/main/res/layout-sw600dp-land/timer_item.xml
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
modified
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- This TimerItem includes the circle because ample space exists. -->
|
||||||
|
<com.best.deskclock.timer.TimerItem
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="4dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
tools:background="@drawable/card_background_for_preview"
|
||||||
|
tools:layout_width="400dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/timer_label"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:hint="@string/add_label"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:focusable="true"
|
||||||
|
app:drawableStartCompat="@drawable/ic_label"
|
||||||
|
app:drawableTint="?attr/colorOnSurfaceVariant"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/delete_timer"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/delete_timer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:src="@drawable/ic_delete"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/delete"
|
||||||
|
app:tint="?attr/colorPrimary"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.best.deskclock.widget.CircleButtonsLayout
|
||||||
|
android:id="@+id/circle_container"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:focusable="true"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintHeight_max="220dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/timer_add_time_button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/timer_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:ignore="InconsistentLayout">
|
||||||
|
|
||||||
|
<com.best.deskclock.timer.TimerCircleView
|
||||||
|
android:id="@+id/timer_time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:ignore="InconsistentLayout" />
|
||||||
|
|
||||||
|
<com.best.deskclock.widget.AutoSizingTextView
|
||||||
|
android:id="@+id/timer_time_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:includeFontPadding="false"
|
||||||
|
android:textSize="40sp"
|
||||||
|
tools:text="01:23" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/reset"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_reset"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/reset"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
</com.best.deskclock.widget.CircleButtonsLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/timer_edit_new_duration_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="7dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_edit"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
app:tint="?android:attr/textColor"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/timer_add_time_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/play_pause"
|
||||||
|
tools:text="+ 1:00" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/play_pause"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:contentDescription="@string/timer_start"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/timer_add_time_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/timer_add_time_button"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:icon="@drawable/ic_fab_play" />
|
||||||
|
|
||||||
|
</com.best.deskclock.timer.TimerItem>
|
|
@ -14,6 +14,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="4dp"
|
android:layout_marginVertical="4dp"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
tools:background="@drawable/card_background_for_preview">
|
tools:background="@drawable/card_background_for_preview">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
app:drawableStartCompat="@drawable/ic_label"
|
app:drawableStartCompat="@drawable/ic_label"
|
||||||
app:drawableTint="?attr/colorOnSurfaceVariant"
|
app:drawableTint="?attr/colorOnSurfaceVariant"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/delete_timer"
|
app:layout_constraintEnd_toStartOf="@+id/delete_timer"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
|
@ -50,26 +51,30 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<com.best.deskclock.widget.CircleButtonsLayout
|
||||||
android:id="@+id/circle_container"
|
android:id="@+id/circle_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginTop="5dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintHeight_max="240dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/timer_add_time_button"
|
||||||
app:layout_constraintTop_toBottomOf="@id/timer_label">
|
app:layout_constraintTop_toBottomOf="@+id/timer_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:ignore="InconsistentLayout">
|
||||||
|
|
||||||
<com.best.deskclock.timer.TimerCircleView
|
<com.best.deskclock.timer.TimerCircleView
|
||||||
android:id="@+id/timer_time"
|
android:id="@+id/timer_time"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
tools:ignore="InconsistentLayout" />
|
||||||
|
|
||||||
<com.best.deskclock.widget.AutoSizingTextView
|
<com.best.deskclock.widget.AutoSizingTextView
|
||||||
android:id="@+id/timer_time_text"
|
android:id="@+id/timer_time_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
@ -79,69 +84,90 @@
|
||||||
android:textSize="40sp"
|
android:textSize="40sp"
|
||||||
tools:text="01:23" />
|
tools:text="01:23" />
|
||||||
|
|
||||||
</FrameLayout>
|
<ImageButton
|
||||||
|
android:id="@+id/reset"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_reset"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/reset"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
</com.best.deskclock.widget.CircleButtonsLayout>
|
||||||
android:id="@+id/timer_total_duration"
|
|
||||||
|
<!-- Only displayed for phones when the timer is reset. See the TimerItem.java file. -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/timer_total_duration_container"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="12dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/timer_add_time_button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/timer_label"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/play_pause">
|
||||||
|
|
||||||
|
<com.best.deskclock.widget.AutoSizingTextView
|
||||||
|
android:id="@+id/timer_total_duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="26sp"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:drawableStartCompat="@drawable/ic_edit"
|
||||||
|
app:drawableTint="?android:attr/textColor"
|
||||||
|
tools:ignore="InconsistentLayout" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- For phones with multiple timers, this icon is not displayed. See the TimerItem.java file. -->
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/timer_edit_new_duration_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
android:paddingStart="@null"
|
android:layout_marginBottom="7dp"
|
||||||
android:paddingEnd="4dp"
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
android:paddingVertical="8dp"
|
android:src="@drawable/ic_edit"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:scaleType="centerInside"
|
||||||
android:gravity="center_vertical"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:textStyle="bold"
|
android:contentDescription="@null"
|
||||||
android:textSize="24sp"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:drawablePadding="8dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:drawableStartCompat="@drawable/ic_hourglass_top"
|
app:tint="?android:attr/textColor"
|
||||||
app:drawableTint="?attr/colorOnSurfaceVariant"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/play_pause"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:ignore="InconsistentLayout" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/timer_add_time_button"
|
android:id="@+id/timer_add_time_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:layout_marginBottom="5dp"
|
android:padding="24dp"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/circle_container"
|
app:layout_constraintBottom_toTopOf="@+id/play_pause"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
tools:text="+ 1:00" />
|
tools:text="+ 1:00" />
|
||||||
|
|
||||||
<ImageButton
|
<!-- Constraints, margins and paddings adjusted in the TimerItem.java file to have a suitable
|
||||||
android:id="@+id/reset"
|
reduced view. -->
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_reset"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/reset"
|
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/play_pause"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/play_pause" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/play_pause"
|
android:id="@+id/play_pause"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="12dp"
|
android:padding="24dp"
|
||||||
android:layout_marginBottom="5dp"
|
|
||||||
android:contentDescription="@string/timer_start"
|
android:contentDescription="@string/timer_start"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintStart_toStartOf="@+id/timer_add_time_button"
|
||||||
app:layout_constraintTop_toBottomOf="@id/circle_container"
|
app:layout_constraintEnd_toEndOf="@+id/timer_add_time_button"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:icon="@drawable/ic_fab_play" />
|
tools:icon="@drawable/ic_fab_play" />
|
||||||
|
|
||||||
|
|
|
@ -22,18 +22,11 @@
|
||||||
app:layout_constraintGuide_percent="0.70" />
|
app:layout_constraintGuide_percent="0.70" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/left_button_end_guide"
|
android:id="@+id/button_center_guide"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintGuide_percent="0.4" />
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
|
||||||
android:id="@+id/right_button_start_guide"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintGuide_percent="0.6" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/timer_label"
|
android:id="@+id/timer_label"
|
||||||
|
@ -69,12 +62,12 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<com.best.deskclock.widget.CircleButtonsLayout
|
||||||
android:id="@+id/circle_container"
|
android:id="@+id/circle_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginHorizontal="12dp"
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginVertical="5dp"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -99,8 +92,36 @@
|
||||||
android:textSize="70sp"
|
android:textSize="70sp"
|
||||||
tools:text="01:23" />
|
tools:text="01:23" />
|
||||||
|
|
||||||
</FrameLayout>
|
<ImageButton
|
||||||
|
android:id="@+id/reset"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_reset"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/reset"
|
||||||
|
app:tint="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
</com.best.deskclock.widget.CircleButtonsLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/timer_edit_new_duration_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:src="@drawable/ic_edit"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
app:tint="?android:attr/textColor"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<!-- For tablets with single timer, the size of this button is adjusted.
|
||||||
|
See the TimerItem.java file. -->
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/timer_add_time_button"
|
android:id="@+id/timer_add_time_button"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -114,25 +135,13 @@
|
||||||
app:layout_constraintWidth_max="320dp"
|
app:layout_constraintWidth_max="320dp"
|
||||||
app:layout_constraintHeight_max="100dp"
|
app:layout_constraintHeight_max="100dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/left_button_end_guide"
|
app:layout_constraintEnd_toStartOf="@+id/button_center_guide"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/circle_container_end_guide"
|
app:layout_constraintTop_toBottomOf="@+id/circle_container_end_guide"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:text="+ 1:00" />
|
tools:text="+ 1:00" />
|
||||||
|
|
||||||
<ImageButton
|
<!-- For tablets in portrait mode with single timer, the size of this button is adjusted.
|
||||||
android:id="@+id/reset"
|
See the TimerItem.java file. -->
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_reset"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/reset"
|
|
||||||
app:tint="?attr/colorPrimary"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/play_pause"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/play_pause" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/play_pause"
|
android:id="@+id/play_pause"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -147,7 +156,7 @@
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
app:layout_constraintWidth_max="320dp"
|
app:layout_constraintWidth_max="320dp"
|
||||||
app:layout_constraintHeight_max="100dp"
|
app:layout_constraintHeight_max="100dp"
|
||||||
app:layout_constraintStart_toEndOf="@+id/right_button_start_guide"
|
app:layout_constraintStart_toEndOf="@+id/button_center_guide"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/circle_container_end_guide"
|
app:layout_constraintTop_toBottomOf="@+id/circle_container_end_guide"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
for different hardware and product builds. -->
|
for different hardware and product builds. -->
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="main_clock_font_size">125sp</dimen>
|
<dimen name="main_clock_font_size">125sp</dimen>
|
||||||
<dimen name="timer_time_text_size">40sp</dimen>
|
|
||||||
|
|
||||||
<item name="city_list_end_guide_percent" type="fraction">0.65</item>
|
<item name="city_list_end_guide_percent" type="fraction">0.65</item>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="main_clock_font_size">64sp</dimen>
|
<dimen name="main_clock_font_size">64sp</dimen>
|
||||||
<dimen name="analog_clock_size">294dp</dimen>
|
<dimen name="analog_clock_size">294dp</dimen>
|
||||||
<dimen name="timer_time_text_size">32sp</dimen>
|
|
||||||
|
|
||||||
<item name="timer_setup_time_bottom_percent" type="fraction">0.2</item>
|
<item name="timer_setup_time_bottom_percent" type="fraction">0.2</item>
|
||||||
<item name="city_list_end_guide_percent" type="fraction">0.55</item>
|
<item name="city_list_end_guide_percent" type="fraction">0.55</item>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 349 KiB After Width: | Height: | Size: 376 KiB |
Binary file not shown.
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 319 KiB |
Binary file not shown.
Before Width: | Height: | Size: 470 KiB After Width: | Height: | Size: 444 KiB |
Loading…
Add table
Add a link
Reference in a new issue