From 881b4dcf831041ef9b8e2a9c2e71645e91e32c4b Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Thu, 2 Jun 2016 20:16:37 -0700 Subject: [PATCH] Ported numpad --- app/src/main/AndroidManifest.xml | 3 +- .../java/com/philliphsu/clock2/Numpad.java | 254 ++++++++++ .../clock2/editalarm/AlarmEditText.java | 43 ++ .../clock2/editalarm/AlarmNumpad.java | 463 ++++++++++++++++++ .../clock2/editalarm/EditAlarmActivity.java | 28 +- .../main/res/color/on_enabled_change_fab.xml | 6 + .../main/res/drawable/ic_backspace_24dp.xml | 9 + app/src/main/res/drawable/ic_done_24dp.xml | 9 + .../main/res/layout/activity_edit_alarm.xml | 12 +- app/src/main/res/layout/content_numpad.xml | 57 +++ app/src/main/res/layout/numpad_alt_button.xml | 2 + app/src/main/res/layout/numpad_backspace.xml | 6 + app/src/main/res/layout/numpad_fab.xml | 14 + app/src/main/res/values/strings.xml | 16 + app/src/main/res/values/styles.xml | 9 + 15 files changed, 927 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/philliphsu/clock2/Numpad.java create mode 100644 app/src/main/java/com/philliphsu/clock2/editalarm/AlarmEditText.java create mode 100644 app/src/main/java/com/philliphsu/clock2/editalarm/AlarmNumpad.java create mode 100644 app/src/main/res/color/on_enabled_change_fab.xml create mode 100644 app/src/main/res/drawable/ic_backspace_24dp.xml create mode 100644 app/src/main/res/drawable/ic_done_24dp.xml create mode 100644 app/src/main/res/layout/content_numpad.xml create mode 100644 app/src/main/res/layout/numpad_alt_button.xml create mode 100644 app/src/main/res/layout/numpad_backspace.xml create mode 100644 app/src/main/res/layout/numpad_fab.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4338caa..6588976 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,7 +41,8 @@ android:name=".editalarm.EditAlarmActivity" android:label="@string/title_activity_edit_alarm" android:parentActivityName=".MainActivity" - android:theme="@style/AppTheme.NoActionBar"> + android:theme="@style/AppTheme.NoActionBar" + android:windowSoftInputMode="adjustNothing"> diff --git a/app/src/main/java/com/philliphsu/clock2/Numpad.java b/app/src/main/java/com/philliphsu/clock2/Numpad.java new file mode 100644 index 0000000..76cc320 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/Numpad.java @@ -0,0 +1,254 @@ +package com.philliphsu.clock2; + +import android.content.Context; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.Space; +import android.widget.TableLayout; +import android.widget.TableRow; + +import java.util.Arrays; + +import butterknife.Bind; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by Phillip Hsu on 6/2/2016. + */ +public abstract class Numpad extends TableLayout { + private static final String TAG = "Numpad"; + private static final int NUM_COLUMNS = 3; + private static final int RADIX_10 = 10; + protected static final int UNMODIFIED = -1; + + // Derived classes need to build this themselves via buildBackspace(). + private ImageButton mBackspace; + private ImageButton mCollapse; // TODO: useless? + private int[] mInput; + private int mCount = 0; + private KeyListener mKeyListener; + + @Bind({ R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four, + R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine }) + Button[] mButtons; + + public interface KeyListener { + void onNumberInput(String number); + void onCollapse(); // TODO: useless? + void onBackspace(String newStr); + void onLongBackspace(); + } + + public Numpad(Context context) { + super(context); + init(); + } + + public Numpad(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public void setKeyListener(@NonNull KeyListener listener) { + mKeyListener = listener; + } + + protected KeyListener getKeyListener() { + return mKeyListener; + } + + protected abstract int capacity(); + + protected final void enable(int lowerLimitInclusive, int upperLimitExclusive) { + if (lowerLimitInclusive < 0 || upperLimitExclusive > mButtons.length) + throw new IndexOutOfBoundsException("Upper limit out of range"); + + for (int i = 0; i < mButtons.length; i++) + mButtons[i].setEnabled(i >= lowerLimitInclusive && i < upperLimitExclusive); + } + + protected final int valueAt(int index) { + checkIndexWithinBounds(index); + return mInput[index]; + } + + protected final int count() { + return mCount; + } + + protected final int getInput() { + String currentInput = ""; + for (int i : mInput) + if (i != UNMODIFIED) + currentInput += i; + return Integer.parseInt(currentInput); + } + + protected final void buildBackspace(int r, int c) { + mBackspace = (ImageButton) buildButton(R.layout.numpad_backspace, r, c); + + mBackspace.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + backspace(); + } + }); + + mBackspace.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + return longBackspace(); + } + }); + } + + protected final void newRow() { + TableRow newRow = new TableRow(getContext()); + newRow.setLayoutParams(new TableRow.LayoutParams()); + addView(newRow); + for (int i = 0; i < NUM_COLUMNS; i++) { + Space s = new Space(getContext()); + setButtonLayoutParams(s); + newRow.addView(s); + } + } + + protected final View buildButton(@LayoutRes int buttonRes, int r, int c) { + View button = View.inflate(getContext(), buttonRes, null); + setButtonLayoutParams(button); + replace(r, c, button); + return button; + } + + @OnClick({ R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four, + R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine }) + protected void onClick(Button button) { + onClick(button, mCount); + } + + public void onClick(Button button, int at) { + if (mCount < mInput.length) { + checkIndexWithinBounds(at); + mInput[at] = Integer.parseInt(button.getText().toString()); + mCount++; + } + } + + /** Performs an artificial click on the Button with the specified digit. */ + protected final void performClick(int digit) { + if (digit < 0 || digit >= mButtons.length) + throw new ArrayIndexOutOfBoundsException("No Button with digit " + digit); + if (!mButtons[digit].isEnabled()) + throw new IllegalArgumentException("Button " + digit + " is disabled. " + + "Did you call AlarmNumpad.setInput(String) with an invalid time?"); + onClick(mButtons[digit]); + } + + /** Performs an artificial click on the Button with the specified digit. */ + protected final void performClick(char charDigit) { + performClick(asDigit(charDigit)); + } + + protected void setInput(int... digits) { + if (digits.length != mInput.length) + throw new IllegalArgumentException("Input arrays not the same length"); + for (int i = 0; i < digits.length; i++) { + if (digits[i] < 0 || digits[i] > 9) + throw new IllegalArgumentException("Element in input out of range"); + if (!mButtons[i].isEnabled()) + throw new IllegalStateException("Button with digit " + digits[i] + " is disabled"); + mInput[i] = digits[i]; + } + } + + protected void backspace() { + if (mCount - 1 >= 0) { + mInput[--mCount] = UNMODIFIED; + } + } + + // public to allow hosts of this numpad to modify its contents + public void backspace(int at) { + if (at < 0 || at > mInput.length /* at == mInput.length is valid */) + throw new IndexOutOfBoundsException("Cannot backspace on index " + at); + if (at - 1 >= 0) { + mInput[--at] = UNMODIFIED; + mCount--; + } + } + + protected boolean longBackspace() { + Arrays.fill(mInput, UNMODIFIED); + mCount = 0; + return true; + } + + protected void setBackspaceEnabled(boolean enabled) { + mBackspace.setEnabled(enabled); + } + + protected final void notifyOnNumberInputListener(String number) { + checkKeyListenerSet(); + mKeyListener.onNumberInput(number); + } + + protected final void notifyOnBackspaceListener(String newStr) { + checkKeyListenerSet(); + mKeyListener.onBackspace(newStr); + } + + protected final void notifyOnLongBackspaceListener() { + checkKeyListenerSet(); + mKeyListener.onLongBackspace(); + } + + private void setButtonLayoutParams(View target) { + target.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.MATCH_PARENT, 1)); + } + + private void replace(int r, int c, View newView) { + checkLocation(r, c); + TableRow row = (TableRow) getChildAt(r); + row.removeViewAt(c); + row.addView(newView, c); + } + + // Checks if the specified location in the View hierarchy exists. + private void checkLocation(int r, int c) { + if (r < 0 || r >= getChildCount()) + throw new IndexOutOfBoundsException("No TableRow at row " + r); + if (c < 0 || c >= NUM_COLUMNS) + throw new IndexOutOfBoundsException("No column " + c + " at row " + r); + } + + private void checkIndexWithinBounds(int i) { + if (i < 0 || i >= mInput.length) { + throw new ArrayIndexOutOfBoundsException("Index " + i + "out of bounds"); + } + } + + private void checkKeyListenerSet() { + if (null == mKeyListener) + throw new NullPointerException("Numpad Key listener not set"); + } + + private int asDigit(char charDigit) { + if (!Character.isDigit(charDigit)) + throw new IllegalArgumentException("Character is not a digit"); + return Character.digit(charDigit, RADIX_10); + } + + private void init() { + View.inflate(getContext(), R.layout.content_numpad, this); + ButterKnife.bind(this); + if (capacity() < 0) + throw new IllegalArgumentException("Negative capacity"); + mInput = new int[capacity()]; + Arrays.fill(mInput, UNMODIFIED); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmEditText.java b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmEditText.java new file mode 100644 index 0000000..688bc67 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmEditText.java @@ -0,0 +1,43 @@ +package com.philliphsu.clock2.editalarm; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; + +/** + * Created by Phillip Hsu on 6/2/2016. + */ +public class AlarmEditText extends android.support.v7.widget.AppCompatEditText { + + private OnBackPressListener mOnBackPressListener; + + public AlarmEditText(Context context) { + super(context); + } + + public AlarmEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlarmEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_UP + && mOnBackPressListener != null) { + mOnBackPressListener.onBackPress(); + } + return super.dispatchKeyEvent(event); // See http://stackoverflow.com/a/5993196/5055032 + } + + public void setOnBackPressListener(OnBackPressListener onBackPressListener) { + mOnBackPressListener = onBackPressListener; + } + + public interface OnBackPressListener { + void onBackPress(); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmNumpad.java b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmNumpad.java new file mode 100644 index 0000000..ff26aee --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmNumpad.java @@ -0,0 +1,463 @@ +package com.philliphsu.clock2.editalarm; + +import android.content.Context; +import android.support.annotation.IntDef; +import android.support.design.widget.FloatingActionButton; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; + +import com.philliphsu.clock2.Numpad; +import com.philliphsu.clock2.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by Phillip Hsu on 6/2/2016. + */ +public class AlarmNumpad extends Numpad { + private static final String TAG = "AlarmNumpad"; + + // Time can be represented with maximum of 4 digits + private static final int MAX_DIGITS = 4; + + // Formatted time string has a maximum of 8 characters + // in the 12-hour clock, e.g 12:59 AM. Although the 24-hour + // clock should be capped at 5 characters, the difference + // is not significant enough to deal with the separate cases. + private static final int MAX_CHARS = 8; + + private static final int UNSPECIFIED = -1; + private static final int AM = 0; + private static final int PM = 1; + private static final int HRS_24 = 2; + + @IntDef({ UNSPECIFIED, AM, PM, HRS_24 }) // Specifies the accepted constants + @Retention(RetentionPolicy.SOURCE) // Usages do not need to be recorded in .class files + private @interface AmPmState {} + + private Button leftAlt; // AM or :00 + private Button rightAlt; // PM or :30 + private FloatingActionButton fab; + private final StringBuilder mFormattedInput = new StringBuilder(MAX_CHARS); + + @AmPmState + private int mAmPmState = UNSPECIFIED; + + public AlarmNumpad(Context context) { + this(context, null); + } + + public AlarmNumpad(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** Returns the hour of day (0-23) regardless of clock system */ + public int getHours() { + if (!checkTimeValid()) + throw new IllegalStateException("Cannot call getHours() until legal time inputted"); + int hours = count() < 4 ? valueAt(0) : valueAt(0) * 10 + valueAt(1); + if (hours == 12) { + switch (mAmPmState) { + case AM: + return 0; + case PM: + case HRS_24: + return 12; + default: + break; + } + } + + // AM/PM clock needs value offset + return hours + (mAmPmState == PM ? 12 : 0); + } + + public int getMinutes() { + if (!checkTimeValid()) + throw new IllegalStateException("Cannot call getMinutes() until legal time inputted"); + return count() < 4 ? valueAt(1) * 10 + valueAt(2) : valueAt(2) * 10 + valueAt(3); + } + + /** + * Checks if the input stored so far qualifies as a valid time. + * For this to return {@code true}, the hours, minutes AND AM/PM + * state must be set. + */ + public boolean checkTimeValid() { + if (mAmPmState == UNSPECIFIED || mAmPmState == HRS_24 && count() < 3) + return false; + // AM or PM can only be set if the time was already valid previously, so we don't need + // to check for them. + return true; + } + + @Override + protected int capacity() { + return MAX_DIGITS; + } + + @Override + protected void onClick(Button button) { + super.onClick(button); + // Format and store the input as text + inputNumber(button.getText()); + notifyOnNumberInputListener(mFormattedInput.toString()); + updateNumpadStates(); + } + + @Override + protected void backspace() { + int len = mFormattedInput.length(); + if (!is24HourFormat() && mAmPmState != UNSPECIFIED) { + mAmPmState = UNSPECIFIED; + // Delete starting from index of space to end + mFormattedInput.delete(mFormattedInput.indexOf(" "), len); + } else { + super.backspace(); + mFormattedInput.delete(len - 1, len); + if (count() == 3) { + // Move the colon from its 4-digit position to its 3-digit position, + // unless doing so gives an invalid time. + // e.g. 17:55 becomes 1:75, which is invalid. + // All 3-digit times in the 12-hour clock at this point should be + // valid. The limits <=155 and (>=200 && <=235) are really only + // imposed on the 24-hour clock, and were chosen because 4-digit times + // in the 24-hour clock can only go up to 15:5[0-9] or be within the range + // [20:00, 23:59] if they are to remain valid when they become three digits. + // The is24HourFormat() check is therefore unnecessary. + int value = getInput(); + if (value <= 155 || value >= 200 && value <= 235) { + mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":")); + mFormattedInput.insert(1, ":"); + } + } else if (count() == 2) { + // Remove the colon + mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":")); + } + } + + notifyOnBackspaceListener(mFormattedInput.toString()); + updateNumpadStates(); + } + + @Override + protected boolean longBackspace() { + boolean consumed = super.longBackspace(); + mFormattedInput.delete(0, mFormattedInput.length()); + notifyOnLongBackspaceListener(); + updateNumpadStates(); + mAmPmState = UNSPECIFIED; + return consumed; + } + + public void setTime(int hours, int minutes) { + if (hours < 0 || hours > 23) + throw new IllegalArgumentException("Illegal hours: " + hours); + if (minutes < 0 || minutes > 59) + throw new IllegalArgumentException("Illegal minutes: " + minutes); + + // Internal representation of the time has been checked for legality. + // Now we need to format it depending on the user's clock system. + // If 12-hour clock, can't set mAmPmState yet or else this interferes + // with the button state update mechanism. Instead, cache the state + // the hour would resolve to in a local variable and set it after + // all digits are inputted. + int amPmState; + if (!is24HourFormat()) { + // Convert 24-hour times into 12-hour compatible times. + if (hours == 0) { + hours = 12; + amPmState = AM; + } else if (hours == 12) { + amPmState = PM; + } else if (hours > 12) { + hours -= 12; + amPmState = PM; + } else { + amPmState = AM; + } + } else { + amPmState = HRS_24; + } + + // Only if on 24-hour clock, zero-pad single digit hours. + // Zero cannot be the first digit of any time in the 12-hour clock. + String strHrs = is24HourFormat() + ? String.format("%02d", hours) + : String.valueOf(hours); + String strMins = String.format("%02d", minutes); // Zero-pad single digit minutes + + // TODO: Do this off the main thread + for (int i = 0; i < strHrs.length(); i++) + performClick(strHrs.charAt(i)); + for (int i = 0; i < strMins.length(); i++) + performClick(strMins.charAt(i)); + + mAmPmState = amPmState; + if (mAmPmState != HRS_24) { + onAltButtonClick(mAmPmState == AM ? leftAlt : rightAlt); + } + } + + public String getTime() { + return mFormattedInput.toString(); + } + + private void updateAltButtonStates() { + if (count() == 0) { + // No input, no access! + leftAlt.setEnabled(false); + rightAlt.setEnabled(false); + } else if (count() == 1) { + // Any of 0-9 inputted, always have access in either clock. + leftAlt.setEnabled(true); + rightAlt.setEnabled(true); + } else if (count() == 2) { + // Any 2 digits that make a valid hour for either clock are eligible for access + int time = getInput(); + boolean validTwoDigitHour = is24HourFormat() ? time <= 23 : time >= 10 && time <= 12; + leftAlt.setEnabled(validTwoDigitHour); + rightAlt.setEnabled(validTwoDigitHour); + } else if (count() == 3) { + if (is24HourFormat()) { + // For the 24-hour clock, no access at all because + // two more digits (00 or 30) cannot be added to 3 digits. + leftAlt.setEnabled(false); + rightAlt.setEnabled(false); + } else { + // True for any 3 digits, if AM/PM not already entered + boolean enabled = mAmPmState == UNSPECIFIED; + leftAlt.setEnabled(enabled); + rightAlt.setEnabled(enabled); + } + } else if (count() == MAX_DIGITS) { + // If all 4 digits are filled in, the 24-hour clock has absolutely + // no need for the alt buttons. However, The 12-hour clock has + // complete need of them, if not already used. + boolean enabled = !is24HourFormat() && mAmPmState == UNSPECIFIED; + leftAlt.setEnabled(enabled); + rightAlt.setEnabled(enabled); + } + } + + private void updateBackspaceState() { + setBackspaceEnabled(count() > 0); + } + + private void updateFabState() { + // Special case of 0 digits is legal (that is the default hint time) + if (count() == 0) { + fab.setEnabled(true); + return; + } + // Minimum of 3 digits is required + if (count() < 3) { + fab.setEnabled(false); + return; + } + + if (is24HourFormat()) { + int time = getInput(); + fab.setEnabled(time % 100 <= 59); + } else { + // If on 12-hour clock, FAB will never be enabled + // until AM or PM is explicitly clicked. + fab.setEnabled(mAmPmState != UNSPECIFIED); + } + } + + private void updateNumpadStates() { + updateAltButtonStates(); + updateFabState(); + updateBackspaceState(); + updateNumberKeysStates(); + } + + private void updateNumberKeysStates() { + int cap = 10; // number of buttons + boolean is24hours = is24HourFormat(); + + if (count() == 0) { + enable(is24hours ? 0 : 1, cap); + return; + } else if (count() == MAX_DIGITS) { + enable(0, 0); + return; + } + + int time = getInput(); + if (is24hours) { + if (count() == 1) { + enable(0, time < 2 ? cap : 6); + } else if (count() == 2) { + enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 6); + } else if (count() == 3) { + if (time >= 236) { + enable(0, 0); + } else { + enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0); + } + } + } else { + if (count() == 1) { + if (time == 0) { + throw new IllegalStateException("12-hr format, zeroth digit = 0?"); + } else { + enable(0, 6); + } + } else if (count() == 2 || count() == 3) { + if (time >= 126) { + enable(0, 0); + } else { + if (time >= 100 && time <= 125 && mAmPmState != UNSPECIFIED) { + // Could legally input fourth digit, if not for the am/pm state already set + enable(0, 0); + } else { + enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0); + } + } + } + } + } + + // Helper method for inputting each character of an altBtn. + private void onAltButtonClick(Button altBtn) { + if (leftAlt != altBtn && rightAlt != altBtn) + throw new IllegalArgumentException("Not called with one of the alt buttons"); + + // Manually insert special characters for 12-hour clock + if (!is24HourFormat()) { + if (count() <= 2) { + // The colon is inserted for you + performClick(0); + performClick(0); + } + // text is AM or PM, so include space before + mFormattedInput.append(' ').append(altBtn.getText()); + mAmPmState = leftAlt == altBtn ? AM : PM; + // Digits will be shown for you on click, but not AM/PM + notifyOnNumberInputListener(mFormattedInput.toString()); + } else { + // Need to consider each character individually, + // as we are only interested in storing the digits + CharSequence text = altBtn.getText(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (Character.isDigit(c)) { + // Convert char to digit and perform click + // Colon is added for you + performClick(c); + } + } + mAmPmState = HRS_24; + } + + updateNumpadStates(); + } + + private boolean is24HourFormat() { + return DateFormat.is24HourFormat(getContext()); + } + + private void inputNumber(CharSequence number) { + mFormattedInput.append(number); + // Add colon if necessary, depending on how many digits entered so far + if (count() == 3) { + // Insert a colon + int digits = getInput(); + if (digits >= 60 && digits < 100 || digits >= 160 && digits < 200) { + // From 060-099 (really only to 095, but might as well go up to 100) + // From 160-199 (really only to 195, but might as well go up to 200), + // time does not exist if colon goes at pos. 1 + mFormattedInput.insert(2, ':'); + // These times only apply to the 24-hour clock, and if we're here, + // the time is not legal yet. So we can't set mAmPmState here for + // either clock. + // The 12-hour clock can only have mAmPmState set when AM/PM are clicked. + } else { + // A valid time exists if colon is at pos. 1 + mFormattedInput.insert(1, ':'); + // We can set mAmPmState here (and not in the above case) because + // the time here is legal in 24-hour clock + if (is24HourFormat()) { + mAmPmState = HRS_24; + } + } + } else if (count() == MAX_DIGITS) { + // Colon needs to move, so remove the colon previously added + mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":")); + mFormattedInput.insert(2, ':'); + + // Time is legal in 24-hour clock + if (is24HourFormat()) { + mAmPmState = HRS_24; + } + } + + // Moved to onClick() + //notifyOnNumberInputListener(mFormattedInput.toString()); + } + + public interface KeyListener extends Numpad.KeyListener { + void onAcceptChanges(); + } + + private void init() { + // Build alternative action buttons + leftAlt = (Button) buildButton(R.layout.numpad_alt_button, 3, 0); + rightAlt = (Button) buildButton(R.layout.numpad_alt_button, 3, 2); + leftAlt.setText(is24HourFormat() ? R.string.left_alt_24hr : R.string.am); + rightAlt.setText(is24HourFormat() ? R.string.right_alt_24hr : R.string.pm); + + leftAlt.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onAltButtonClick(leftAlt); + } + }); + + rightAlt.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onAltButtonClick(rightAlt); + } + }); + + newRow(); + buildBackspace(getChildCount() - 1, 2); +// buildCollapse(getChildCount() - 1, 0); + // The FAB is wrapped in a FrameLayout + FrameLayout frame = (FrameLayout) + buildButton(R.layout.numpad_fab, getChildCount() - 1, 1); + fab = (FloatingActionButton) frame.getChildAt(0); + //fab.setBackgroundTintList(getResources().getColorStateList(R.color.on_enabled_change_fab)); + + fab.setEnabled(false); + + fab.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (count() == 0) { + // Default time + setTime(0, 0); + } + KeyListener kl; + try { + kl = (KeyListener) getKeyListener(); + } catch (ClassCastException e) { + throw new ClassCastException("Using AlarmNumpad with Numpad.KeyListener instead of AlarmNumpad.KeyListener"); + } catch (NullPointerException e) { + throw new NullPointerException("Numpad's KeyListener is not set"); + } + kl.onAcceptChanges(); + } + }); + + updateNumpadStates(); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java index 9b5d6e8..fcb3ffa 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java @@ -9,6 +9,8 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.SwitchCompat; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; @@ -24,13 +26,15 @@ import java.util.Date; import butterknife.Bind; import butterknife.OnClick; +import butterknife.OnTouch; import static android.text.format.DateFormat.getTimeFormat; import static com.philliphsu.clock2.DaysOfWeek.NUM_DAYS; import static com.philliphsu.clock2.DaysOfWeek.SATURDAY; import static com.philliphsu.clock2.DaysOfWeek.SUNDAY; -public class EditAlarmActivity extends BaseActivity { +public class EditAlarmActivity extends BaseActivity implements AlarmEditText.OnBackPressListener { + private static final String TAG = "EditAlarmActivity"; public static final String EXTRA_ALARM_ID = "com.philliphsu.clock2.editalarm.extra.ALARM_ID"; private static final int REQUEST_PICK_RINGTONE = 0; @@ -42,12 +46,13 @@ public class EditAlarmActivity extends BaseActivity { @Bind(R.id.save) Button mSave; @Bind(R.id.delete) Button mDelete; @Bind(R.id.on_off) SwitchCompat mSwitch; - @Bind(R.id.input_time) EditText mTimeText; + @Bind(R.id.input_time) AlarmEditText mTimeText; @Bind({R.id.day0, R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6}) ToggleButton[] mDays; @Bind(R.id.label) EditText mLabel; @Bind(R.id.ringtone) Button mRingtone; @Bind(R.id.vibrate) CheckBox mVibrate; + @Bind(R.id.numpad) AlarmNumpad mNumpad; @Override protected void onCreate(Bundle savedInstanceState) { @@ -76,8 +81,12 @@ public class EditAlarmActivity extends BaseActivity { ab.setDisplayShowTitleEnabled(true); ab.setTitle(title); } + // Editing alarm so don't show + mNumpad.setVisibility(View.GONE); } else { // TODO default values + // No alarm retrieved with this id + mNumpad.setVisibility(View.VISIBLE); } } else { // Initializing to Settings.System.DEFAULT_ALARM_ALERT_URI will show @@ -87,8 +96,10 @@ public class EditAlarmActivity extends BaseActivity { // Compare with getDefaultUri(int), which returns the symbolic URI instead of the // actual sound URI. For TYPE_ALARM, this actually returns the same constant. mSelectedRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + mNumpad.setVisibility(View.VISIBLE); } updateRingtoneButtonText(); + mTimeText.setOnBackPressListener(this); } @Override @@ -128,6 +139,19 @@ public class EditAlarmActivity extends BaseActivity { return 0; } + /** Handle back press when virtual keyboard is shown */ + @Override + public void onBackPress() { + // TODO: Hide your numpad. + mNumpad.setVisibility(View.GONE); + } + + @OnTouch(R.id.input_time) + boolean touch(MotionEvent event) { + mNumpad.setVisibility(View.VISIBLE); + return true; + } + @OnClick(R.id.ringtone) void ringtone() { Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); diff --git a/app/src/main/res/color/on_enabled_change_fab.xml b/app/src/main/res/color/on_enabled_change_fab.xml new file mode 100644 index 0000000..bbc8af3 --- /dev/null +++ b/app/src/main/res/color/on_enabled_change_fab.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_backspace_24dp.xml b/app/src/main/res/drawable/ic_backspace_24dp.xml new file mode 100644 index 0000000..54ad8c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_backspace_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_done_24dp.xml b/app/src/main/res/drawable/ic_done_24dp.xml new file mode 100644 index 0000000..fb13677 --- /dev/null +++ b/app/src/main/res/drawable/ic_done_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_edit_alarm.xml b/app/src/main/res/layout/activity_edit_alarm.xml index 495eac6..cb79d0e 100644 --- a/app/src/main/res/layout/activity_edit_alarm.xml +++ b/app/src/main/res/layout/activity_edit_alarm.xml @@ -74,7 +74,7 @@ android:layout_centerVertical="true"/> - + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_numpad.xml b/app/src/main/res/layout/content_numpad.xml new file mode 100644 index 0000000..6ba2957 --- /dev/null +++ b/app/src/main/res/layout/content_numpad.xml @@ -0,0 +1,57 @@ + + + + + + +