blob: fc5c8599d9402011ecf08039142a856f8bb30021 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock.timer;
import static com.android.deskclock.FabContainer.FAB_REQUEST_FOCUS;
import static com.android.deskclock.FabContainer.FAB_SHRINK_AND_EXPAND;
import android.content.Context;
import android.content.res.Resources;
import android.text.BidiFormatter;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.IdRes;
import com.android.deskclock.FabContainer;
import com.android.deskclock.FormattedTextUtils;
import com.android.deskclock.R;
import com.android.deskclock.uidata.UiDataModel;
import java.io.Serializable;
import java.util.Arrays;
public class TimerSetupView extends LinearLayout implements View.OnClickListener,
View.OnLongClickListener {
private final int[] mInput = { 0, 0, 0, 0, 0, 0 };
private int mInputPointer = -1;
private final CharSequence mTimeTemplate;
private TextView mTimeView;
private View mDeleteView;
private TextView[] mDigitViews;
/** Updates to the fab are requested via this container. */
private FabContainer mFabContainer;
public TimerSetupView(Context context) {
this(context, null /* attrs */);
}
public TimerSetupView(Context context, AttributeSet attrs) {
super(context, attrs);
final BidiFormatter bf = BidiFormatter.getInstance(false /* rtlContext */);
final String hoursLabel = bf.unicodeWrap(context.getString(R.string.hours_label));
final String minutesLabel = bf.unicodeWrap(context.getString(R.string.minutes_label));
final String secondsLabel = bf.unicodeWrap(context.getString(R.string.seconds_label));
// Create a formatted template for "00h 00m 00s".
mTimeTemplate = TextUtils.expandTemplate("^1^4 ^2^5 ^3^6",
bf.unicodeWrap("^1"),
bf.unicodeWrap("^2"),
bf.unicodeWrap("^3"),
FormattedTextUtils.formatText(hoursLabel, new RelativeSizeSpan(0.5f)),
FormattedTextUtils.formatText(minutesLabel, new RelativeSizeSpan(0.5f)),
FormattedTextUtils.formatText(secondsLabel, new RelativeSizeSpan(0.5f)));
LayoutInflater.from(context).inflate(R.layout.timer_setup_view, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTimeView = findViewById(R.id.timer_setup_time);
mDeleteView = findViewById(R.id.timer_setup_delete);
mDigitViews = new TextView[] {
findViewById(R.id.timer_setup_digit_0),
findViewById(R.id.timer_setup_digit_1),
findViewById(R.id.timer_setup_digit_2),
findViewById(R.id.timer_setup_digit_3),
findViewById(R.id.timer_setup_digit_4),
findViewById(R.id.timer_setup_digit_5),
findViewById(R.id.timer_setup_digit_6),
findViewById(R.id.timer_setup_digit_7),
findViewById(R.id.timer_setup_digit_8),
findViewById(R.id.timer_setup_digit_9),
};
// Initialize the digit buttons.
final UiDataModel uidm = UiDataModel.getUiDataModel();
for (final TextView digitView : mDigitViews) {
final int digit = getDigitForId(digitView.getId());
digitView.setText(uidm.getFormattedNumber(digit, 1));
digitView.setOnClickListener(this);
}
TextView doubleZero = findViewById(R.id.timer_setup_digit_00);
doubleZero.setText(uidm.getFormattedNumber(0, 2));
doubleZero.setOnClickListener(this);
mDeleteView.setOnClickListener(this);
mDeleteView.setOnLongClickListener(this);
updateTime();
}
public void setFabContainer(FabContainer fabContainer) {
mFabContainer = fabContainer;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
View view = null;
if (keyCode == KeyEvent.KEYCODE_DEL) {
view = mDeleteView;
} else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
view = mDigitViews[keyCode - KeyEvent.KEYCODE_0];
}
if (view != null) {
final boolean result = view.performClick();
if (result && hasValidInput()) {
mFabContainer.updateFab(FAB_REQUEST_FOCUS);
}
return result;
}
return false;
}
@Override
public void onClick(View view) {
if (view == mDeleteView) {
delete();
} else if (view.getId() == R.id.timer_setup_digit_00) {
append(0);
append(0);
} else {
append(getDigitForId(view.getId()));
}
}
@Override
public boolean onLongClick(View view) {
if (view == mDeleteView) {
reset();
updateFab();
return true;
}
return false;
}
private int getDigitForId(@IdRes int id) {
if (id == R.id.timer_setup_digit_0) {
return 0;
} else if (id == R.id.timer_setup_digit_1) {
return 1;
} else if (id == R.id.timer_setup_digit_2) {
return 2;
} else if (id == R.id.timer_setup_digit_3) {
return 3;
} else if (id == R.id.timer_setup_digit_4) {
return 4;
} else if (id == R.id.timer_setup_digit_5) {
return 5;
} else if (id == R.id.timer_setup_digit_6) {
return 6;
} else if (id == R.id.timer_setup_digit_7) {
return 7;
} else if (id == R.id.timer_setup_digit_8) {
return 8;
} else if (id == R.id.timer_setup_digit_9) {
return 9;
}
throw new IllegalArgumentException("Invalid id: " + id);
}
private void updateTime() {
final int seconds = mInput[1] * 10 + mInput[0];
final int minutes = mInput[3] * 10 + mInput[2];
final int hours = mInput[5] * 10 + mInput[4];
final UiDataModel uidm = UiDataModel.getUiDataModel();
SpannableString text = new SpannableString(TextUtils.expandTemplate(mTimeTemplate,
uidm.getFormattedNumber(hours, 2),
uidm.getFormattedNumber(minutes, 2),
uidm.getFormattedNumber(seconds, 2)));
final Resources r = getResources();
int endIdx = text.length();
int startIdx = seconds > 0 ? 8 : endIdx;
startIdx = minutes > 0 ? 4 : startIdx;
startIdx = hours > 0 ? 0 : startIdx;
if (startIdx != endIdx) {
final int highlightColor = r.getColor(R.color.accent_color, getContext().getTheme());
text.setSpan(new ForegroundColorSpan(highlightColor), startIdx, endIdx, 0);
}
mTimeView.setText(text);
mTimeView.setContentDescription(r.getString(R.string.timer_setup_description,
r.getQuantityString(R.plurals.hours, hours, hours),
r.getQuantityString(R.plurals.minutes, minutes, minutes),
r.getQuantityString(R.plurals.seconds, seconds, seconds)));
}
private void updateFab() {
mFabContainer.updateFab(FAB_SHRINK_AND_EXPAND);
}
private void append(int digit) {
if (digit < 0 || digit > 9) {
throw new IllegalArgumentException("Invalid digit: " + digit);
}
// Pressing "0" as the first digit does nothing.
if (mInputPointer == -1 && digit == 0) {
return;
}
// No space for more digits, so ignore input.
if (mInputPointer == mInput.length - 1) {
return;
}
// Append the new digit.
System.arraycopy(mInput, 0, mInput, 1, mInputPointer + 1);
mInput[0] = digit;
mInputPointer++;
updateTime();
// Update TalkBack to read the number being deleted.
mDeleteView.setContentDescription(getContext().getString(
R.string.timer_descriptive_delete,
UiDataModel.getUiDataModel().getFormattedNumber(digit)));
// Update the fab, delete, and divider when we have valid input.
if (mInputPointer == 0) {
updateFab();
}
}
private void delete() {
// Nothing exists to delete so return.
if (mInputPointer < 0) {
return;
}
System.arraycopy(mInput, 1, mInput, 0, mInputPointer);
mInput[mInputPointer] = 0;
mInputPointer--;
updateTime();
// Update TalkBack to read the number being deleted or its original description.
if (mInputPointer >= 0) {
mDeleteView.setContentDescription(getContext().getString(
R.string.timer_descriptive_delete,
UiDataModel.getUiDataModel().getFormattedNumber(mInput[0])));
} else {
mDeleteView.setContentDescription(getContext().getString(R.string.timer_delete));
}
// Update the fab, delete, and divider when we no longer have valid input.
if (mInputPointer == -1) {
updateFab();
}
}
public void reset() {
if (mInputPointer != -1) {
Arrays.fill(mInput, 0);
mInputPointer = -1;
updateTime();
}
}
public boolean hasValidInput() {
return mInputPointer != -1;
}
public long getTimeInMillis() {
final int seconds = mInput[1] * 10 + mInput[0];
final int minutes = mInput[3] * 10 + mInput[2];
final int hours = mInput[5] * 10 + mInput[4];
return seconds * DateUtils.SECOND_IN_MILLIS
+ minutes * DateUtils.MINUTE_IN_MILLIS
+ hours * DateUtils.HOUR_IN_MILLIS;
}
/**
* @return an opaque representation of the state of timer setup
*/
public Serializable getState() {
return Arrays.copyOf(mInput, mInput.length);
}
/**
* @param state an opaque state of this view previously produced by {@link #getState()}
*/
public void setState(Serializable state) {
final int[] input = (int[]) state;
if (input != null && mInput.length == input.length) {
for (int i = 0; i < mInput.length; i++) {
mInput[i] = input[i];
if (mInput[i] != 0) {
mInputPointer = i;
}
}
updateTime();
}
}
}