Update gradle files and BottomSheetPickers library. Remove some conflicting classes and resources.

This commit is contained in:
Phillip Hsu 2017-02-25 01:03:23 -08:00
parent e83bd2d4a1
commit 8584488326
21 changed files with 5 additions and 3572 deletions

View File

@ -90,5 +90,5 @@ dependencies {
compile 'com.android.support:gridlayout-v7:24.2.0'
compile 'com.android.support:cardview-v7:24.2.0'
compile 'com.jakewharton:butterknife:7.0.1'
compile project(":bottomsheetpickers-release")
compile 'com.philliphsu:bottomsheetpickers:2.3.0'
}

View File

@ -1,46 +0,0 @@
/*
* Copyright (C) 2013 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.TextView;
/**
* Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
*/
public class AccessibleTextView extends TextView {
public AccessibleTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(Button.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(Button.class.getName());
}
}

View File

@ -1,200 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import butterknife.ButterKnife;
/**
* Created by Phillip Hsu on 7/16/2016.
*/
public abstract class BaseTimePickerDialog extends BottomSheetDialogFragment {
private static final String TAG = "BaseTimePickerDialog";
private OnTimeSetListener mCallback;
/**
* The callback interface used to indicate the user is done filling in
* the time (they clicked on the 'Set' button).
*/
public interface OnTimeSetListener {
/**
* @param viewGroup The view associated with this listener.
* @param hourOfDay The hour that was set.
* @param minute The minute that was set.
*/
// TODO: Consider removing VG param, since listeners probably won't need to use it....
void onTimeSet(ViewGroup viewGroup, int hourOfDay, int minute);
}
/**
* Empty constructor required for dialog fragment.
* Subclasses do not need to write their own.
*/
public BaseTimePickerDialog() {}
@LayoutRes
protected abstract int contentLayout();
public final void setOnTimeSetListener(OnTimeSetListener callback) {
mCallback = callback;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Not needed for bottom sheet dialogs
// getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
final View view = inflater.inflate(contentLayout(), container, false);
ButterKnife.bind(this, view);
// TODO: We could move this to onCreateDialog() if we cared.
//
// onShow() is called immediately as this DialogFragment is showing, so the
// FAB's animation will barely be noticeable.
// getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
// @Override
// public void onShow(DialogInterface dialog) {
// Log.i(TAG, "onShow()");
// // Animate the FAB into view
// View v = view.findViewById(R.id.fab);
// if (v != null) {
// FloatingActionButton fab = (FloatingActionButton) v;
// fab.show();
// }
// }
// });
return view;
}
protected final void onTimeSet(ViewGroup vg, int hourOfDay, int minute) {
if (mCallback != null) {
mCallback.onTimeSet(vg, hourOfDay, minute);
}
dismiss();
}
// @Override
// public void onResume() {
// super.onResume();
// final View view = getView();
// final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) view.getParent());
// // Copy over the internal callback logic, and also implement our own
// //
// // This callback is set AFTER this Fragment has become visible, so is useless for what
// // you wanted to do (show the FAB during the settling phase).
// behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
// @Override
// public void onStateChanged(@NonNull View bottomSheet, int newState) {
// Log.i(TAG, "onStateChanged(): " + newState);
// if (newState == BottomSheetBehavior.STATE_HIDDEN) {
// dismiss();
// }
// // My logic below
// else if (newState == BottomSheetBehavior.STATE_SETTLING) {
// View fab = view.findViewById(R.id.fab);
// if (fab != null) {
// ((FloatingActionButton) fab).show();
// }
// }
// }
//
// @Override
// public void onSlide(@NonNull View bottomSheet, float slideOffset) {
//
// }
// });
// }
@Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
// Code for AlertDialog style only.
// @NonNull
// @Override
// public Dialog onCreateDialog(Bundle savedInstanceState) {
// // Use an AlertDialog to display footer buttons, rather than
// // re-invent them in our layout.
// AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// builder.setView(contentLayout())
// // The action strings are already defined and localized by the system!
// .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
//
// }
// })
// .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
//
// }
// });
// return builder.create();
// }
// This was an unsatisfactory solution to forcing the bottom sheet to show at its
// fully expanded state. Our anchored FAB and GridLayout buttons would not be visible.
// @Override
// public Dialog onCreateDialog(Bundle savedInstanceState) {
// Dialog dialog = super.onCreateDialog(savedInstanceState);
// //dialog = new BottomSheetDialog(getActivity(), R.style.AppTheme_AppCompatDialog/*crashes our app!*/);
// // We're past onCreate() in the lifecycle, so the activity is alive.
// View view = LayoutInflater.from(getActivity()).inflate(contentLayout(), null);
// /**
// * Adds our view to a ViewGroup that has a BottomSheetBehavior attached. The ViewGroup
// * itself is a child of a CoordinatorLayout.
// * @see {@link BottomSheetDialog#wrapInBottomSheet(int, View, ViewGroup.LayoutParams)}
// */
// dialog.setContentView(view);
// // Bind this fragment, not the internal dialog! (There is a bind(Dialog) API.)
// ButterKnife.bind(this, view);
// final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) view.getParent());
// // When we collapse, collapse all the way. Do not be misled by the "docs" in
// // https://android-developers.blogspot.com.au/2016/02/android-support-library-232.html
// // when it says:
// // "STATE_COLLAPSED: ... the app:behavior_peekHeight attribute (defaults to 0)"
// // While it is true by default, BottomSheetDialogs override this default height.
// This means the sheet is considered "open" even at a height of 0! This is why
// // when you swipe to hide the sheet, the screen remains darkened--indicative
// // of an open dialog.
// behavior.setPeekHeight(0);
// dialog.setOnShowListener(new DialogInterface.OnShowListener() {
// @Override
// public void onShow(DialogInterface dialog) {
// // Every time we show, show at our full height.
// behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
// }
// });
//
// return dialog;
// }
}

View File

@ -1,297 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.content.res.ColorStateList;
import android.support.annotation.CallSuper;
import android.support.annotation.LayoutRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import com.philliphsu.clock2.R;
import java.util.Arrays;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by Phillip Hsu on 7/12/2016.
*
* Successor to the Numpad class that was based on TableLayout.
*
* TODO: Is NumpadTimePicker the only subclass? If so, why do we need this
* superclass? If we move the contents of this class to NumpadTimePicker,
* the implementation of setTheme() would make more sense.
*/
public abstract class GridLayoutNumpad extends GridLayout {
// TODO: change to private?
protected static final int UNMODIFIED = -1;
private static final int COLUMNS = 3;
private int[] mInput;
private int mCount = 0;
private OnInputChangeListener mOnInputChangeListener;
private ColorStateList mTextColors;
int mAccentColor;
@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 })
TextView[] mButtons;
/**
* Informs clients how to output the digits inputted into this numpad.
*/
public interface OnInputChangeListener {
/**
* @param newStr the new value of the input formatted as a
* String after a digit insertion
*/
void onDigitInserted(String newStr);
/**
* @param newStr the new value of the input formatted as a
* String after a digit deletion
*/
void onDigitDeleted(String newStr);
void onDigitsCleared();
}
public GridLayoutNumpad(Context context) {
this(context, null);
}
public GridLayoutNumpad(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void setTheme(Context context, boolean themeDark) {
// Since the Dialog class already set the background color of its entire view tree,
// our background is already colored. Why did we set it in the Dialog class? Because
// we use margins around the numpad, and if we had instead set the background on
// this numpad here, the margins will not be colored. Why not use padding instead
// of margins? It turns out we tried that--replacing each margin attribute
// with the padding counterpart--but we lost the pre-21 FAB inherent bottom margin.
// The buttons are actually of type Button, but we kept references
// to them as TextViews... which is fine since TextView is the superclass
// of Button.
mTextColors = ContextCompat.getColorStateList(context, themeDark?
R.color.numeric_keypad_button_text_dark : R.color.numeric_keypad_button_text);
// AFAIK, the only way to get the user's accent color is programmatically,
// because it is uniquely defined in their app's theme. It is not possible
// for us to reference that via XML (i.e. with ?colorAccent or similar),
// which happens at compile time.
// TOneverDO: Use any other Context to retrieve the accent color. We must use
// the Context param passed to us, because we know this context to be
// NumpadTimePickerDialog.getContext(), which is equivalent to
// NumpadTimePickerDialog.getActivity(). It is from that Activity where we
// get its theme's colorAccent.
mAccentColor = Utils.getThemeAccentColor(context);
for (TextView b : mButtons) {
setTextColor(b);
Utils.setColorControlHighlight(b, mAccentColor);
}
}
void setTextColor(TextView view) {
view.setTextColor(mTextColors);
}
/**
* @return the number of digits we can input
*/
public abstract int capacity();
@LayoutRes
protected abstract int contentLayout();
public final void setOnInputChangeListener(OnInputChangeListener onInputChangeListener) {
mOnInputChangeListener = onInputChangeListener;
}
/**
* Provided only for subclasses so they can retrieve the registered listener
* and fire any custom OnInputChange events they may have defined.
*/
protected final OnInputChangeListener getOnInputChangeListener() {
return mOnInputChangeListener;
}
@CallSuper
protected 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) {
return mInput[index];
}
/**
* @return a defensive copy of the internal array of inputted digits
*/
protected final int[] getDigits() {
int[] digits = new int[mInput.length];
System.arraycopy(mInput, 0, digits, 0, mInput.length);
return digits;
}
/**
* @return the number of digits inputted
*/
public final int count() {
return mCount;
}
/**
* @return the integer represented by the inputted digits
*/
protected final int getInput() {
return Integer.parseInt(getInputString());
}
private String getInputString() {
String currentInput = "";
for (int i : mInput)
if (i != UNMODIFIED)
currentInput += i;
return currentInput;
}
public void delete() {
/*
if (mCount - 1 >= 0) {
mInput[--mCount] = UNMODIFIED;
}
onDigitDeleted(getInputString());
*/
delete(mCount);
}
// TODO: Why do we need this?
@Deprecated
public void delete(int at) {
if (at - 1 >= 0) {
mInput[at - 1] = UNMODIFIED;
mCount--;
onDigitDeleted(getInputString());
}
}
public boolean clear() {
Arrays.fill(mInput, UNMODIFIED);
mCount = 0;
onDigitsCleared();
return true;
}
/**
* Forwards the provided String to the assigned
* {@link OnInputChangeListener OnInputChangeListener}
* after a digit insertion. By default, the String
* forwarded is just the String value of the inserted digit.
* @see #onClick(TextView)
* @param newDigit the formatted String that should be displayed
*/
@CallSuper
protected void onDigitInserted(String newDigit) {
if (mOnInputChangeListener != null) {
mOnInputChangeListener.onDigitInserted(newDigit);
}
}
/**
* Forwards the provided String to the assigned
* {@link OnInputChangeListener OnInputChangeListener}
* after a digit deletion. By default, the String
* forwarded is {@link #getInputString()}.
* @param newStr the formatted String that should be displayed
*/
@CallSuper
protected void onDigitDeleted(String newStr) {
if (mOnInputChangeListener != null) {
mOnInputChangeListener.onDigitDeleted(newStr);
}
}
/**
* Forwards a {@code onDigitsCleared()} event to the assigned
* {@link OnInputChangeListener OnInputChangeListener}.
*/
@CallSuper
protected void onDigitsCleared() {
if (mOnInputChangeListener != null) {
mOnInputChangeListener.onDigitsCleared();
}
}
/**
* Inserts as many of the digits in the given sequence
* into the input as possible. At the end, if any digits
* were inserted, this calls {@link #onDigitInserted(String)}
* with the String value of those digits.
*/
protected final void insertDigits(int... digits) {
if (digits == null)
return;
String newDigits = "";
for (int d : digits) {
if (mCount == mInput.length)
break;
if (d == UNMODIFIED)
continue;
mInput[mCount++] = d;
newDigits += d;
}
if (!newDigits.isEmpty()) {
// By only calling this once after making
// the insertions, we skip all of the
// intermediate callbacks.
onDigitInserted(newDigits);
}
}
@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 })
final void onClick(TextView view) {
if (mCount < mInput.length) {
String textNum = view.getText().toString();
insertDigits(Integer.parseInt(textNum));
}
}
private void init() {
setAlignmentMode(ALIGN_BOUNDS);
setColumnCount(COLUMNS);
View.inflate(getContext(), contentLayout(), this);
ButterKnife.bind(this);
// If capacity() < 0, we let the system throw the exception.
mInput = new int[capacity()];
Arrays.fill(mInput, UNMODIFIED);
}
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (C) 2013 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ViewAnimator;
/**
* Created by Phillip Hsu on 8/17/2016.
*
* A derivative of the AOSP datetimepicker RadialPickerLayout class.
* The animations used here are taken from the DatePickerDialog class.
*
* A ViewAnimator is a subclass of FrameLayout.
*/
public class GridSelectorLayout extends ViewAnimator implements NumbersGrid.OnNumberSelectedListener {
private static final String TAG = "GridSelectorLayout";
// Delay before auto-advancing the page, in ms.
// TODO: If we animate the page change, then we don't need this delay. This was
// my own logic, not ported from AOSP timepicker.
public static final int ADVANCE_PAGE_DELAY = 150;
private static final int ANIMATION_DURATION = 300;
private static final int HOUR_INDEX = NumberGridTimePickerDialog.HOUR_INDEX;
private static final int MINUTE_INDEX = NumberGridTimePickerDialog.MINUTE_INDEX;
// TODO: Rename to HALF_DAY_INDEX?
private static final int AMPM_INDEX = NumberGridTimePickerDialog.AMPM_INDEX;
private static final int HALF_DAY_1 = NumberGridTimePickerDialog.HALF_DAY_1;
private static final int HALF_DAY_2 = NumberGridTimePickerDialog.HALF_DAY_2;
private OnValueSelectedListener mListener;
private boolean mTimeInitialized;
private int mCurrentHoursOfDay;
private int mCurrentMinutes;
private boolean mIs24HourMode;
private int mCurrentItemShowing;
private HoursGrid mHoursGrid = null;
private TwentyFourHoursGrid m24HoursGrid = null;
private MinutesGrid mMinutesGrid;
private final Handler mHandler = new Handler();
private final Animation mInAnimation;
private final Animation mOutAnimation;
public interface OnValueSelectedListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}
public GridSelectorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// Taken from AOSP DatePickerDialog class
// TODO: They look terrible on our views. Create new animations.
mInAnimation = new AlphaAnimation(0.0f, 1.0f);
mInAnimation.setDuration(ANIMATION_DURATION);
mOutAnimation = new AlphaAnimation(1.0f, 0.0f);
mOutAnimation.setDuration(ANIMATION_DURATION);
}
// TODO: Why do we need a Context param? RadialPickerLayout does it too.
public void initialize(Context context, int initialHoursOfDay, int initialMinutes, boolean is24HourMode) {
if (mTimeInitialized) {
Log.e(TAG, "Time has already been initialized.");
return;
}
// *****************************************************************************************
// * TODO: Should we move this block to the Dialog class? This is pretty similar
// * to what AOSP's DatePickerDialog class does. I don't immediately see any
// * code that REALLY needs to be done in this class instead.
mIs24HourMode = is24HourMode;
if (is24HourMode) {
m24HoursGrid = new TwentyFourHoursGrid(context);
m24HoursGrid.initialize(this/*OnNumberSelectedListener*/);
if (initialHoursOfDay >= 12) {
// 24 hour grid is always initialized with 00-11 in the primary position
m24HoursGrid.swapTexts();
}
addView(m24HoursGrid);
} else {
mHoursGrid = new HoursGrid(context);
mHoursGrid.initialize(this/*OnNumberSelectedListener*/);
addView(mHoursGrid);
}
mMinutesGrid = new MinutesGrid(context);
mMinutesGrid.initialize(this/*OnNumberSelectedListener*/);
addView(mMinutesGrid);
setInAnimation(mInAnimation);
setOutAnimation(mOutAnimation);
// *****************************************************************************************
// Initialize the currently-selected hour and minute.
setValueForItem(HOUR_INDEX, initialHoursOfDay);
setValueForItem(MINUTE_INDEX, initialMinutes);
mTimeInitialized = true;
}
void setTheme(Context context, boolean themeDark) {
// TODO: This logic needs to be in the Dialog class, since the am/pm view is contained there.
// mAmPmView.setTheme(context, themeDark);
if (m24HoursGrid != null) {
m24HoursGrid.setTheme(context, themeDark);
} else if (mHoursGrid != null) {
mHoursGrid.setTheme(context, themeDark);
}
mMinutesGrid.setTheme(context, themeDark);
}
public void setTime(int hours, int minutes) {
setValueForItem(HOUR_INDEX, hours);
setValueForItem(MINUTE_INDEX, minutes);
}
public void setOnValueSelectedListener(OnValueSelectedListener listener) {
mListener = listener;
}
/**
* Get the item (hours or minutes) that is currently showing.
*/
public int getCurrentItemShowing() {
if (mCurrentItemShowing != HOUR_INDEX && mCurrentItemShowing != MINUTE_INDEX) {
Log.e(TAG, "Current item showing was unfortunately set to "+mCurrentItemShowing);
return -1;
}
return mCurrentItemShowing;
}
/**
* Set either minutes or hours as showing.
* @param animate True to animate the transition, false to show with no animation.
*/
public void setCurrentItemShowing(int index, boolean animate) {
if (index != HOUR_INDEX && index != MINUTE_INDEX) {
Log.e(TAG, "TimePicker does not support view at index "+index);
return;
}
int lastIndex = getCurrentItemShowing();
mCurrentItemShowing = index;
if (index != lastIndex) {
setInAnimation(animate? mInAnimation : null);
setOutAnimation(animate? mOutAnimation : null);
setDisplayedChild(index);
}
}
// TODO: The Dialog should be telling us that AM/PM was selected, via setAmOrPm().
// @Override
// public void onAmPmSelected(int amOrPm) {
// setValueForItem(AMPM_INDEX, amOrPm);
// mListener.onValueSelected(AMPM_INDEX, amOrPm, false);
// }
@Override
public void onNumberSelected(int number) {
// Flag set to true if this onNumberSelected() event was caused by
// long clicking in a TwentyFourHoursGrid.
boolean fakeHourItemShowing = false;
if (getCurrentItemShowing() == HOUR_INDEX) {
if (!mIs24HourMode) {
// Change the value before passing it through the callback
int amOrPm = getIsCurrentlyAmOrPm();
if (amOrPm == HALF_DAY_1 && number == 12) {
number = 0;
} else if (amOrPm == HALF_DAY_2 && number != 12) {
number += 12;
}
} else {
// Check if we would be changing half-days with the new value.
// This can happen if this selection occurred with a long click.
if (mCurrentHoursOfDay < 12 && number >= 12 || mCurrentHoursOfDay >= 12 && number < 12) {
int newHalfDay = getIsCurrentlyAmOrPm() == HALF_DAY_1 ? HALF_DAY_2 : HALF_DAY_1;
// Update the half-day toggles states
mListener.onValueSelected(AMPM_INDEX, newHalfDay, false);
// Advance the index prematurely to bypass the animation that would otherwise
// be forced on us if we let the listener autoAdvance us.
setCurrentItemShowing(MINUTE_INDEX, false/*animate?*/);
// We need to "trick" the listener to think we're still on HOUR_INDEX.
// When the listener gets the onValueSelected() callback,
// it needs to call our setCurrentItemShowing() with MINUTE_INDEX a second time,
// so it ends up doing nothing. (Recall that the new index must be different from the
// last index for setCurrentItemShowing() to actually change the current item
// showing.) This has the effect of "tricking" the listener to update its
// own states relevant to the HOUR_INDEX, without having it actually autoAdvance
// and forcing an animation on us.
fakeHourItemShowing = true;
}
}
}
final int currentItemShowing = fakeHourItemShowing? HOUR_INDEX : getCurrentItemShowing();
setValueForItem(currentItemShowing, number);
mListener.onValueSelected(currentItemShowing, number,
true/*autoAdvance, not considered for MINUTE_INDEX*/);
}
public int getHours() {
return mCurrentHoursOfDay;
}
public int getMinutes() {
return mCurrentMinutes;
}
/**
* If the hours are showing, return the current hour. If the minutes are showing, return the
* current minute.
*/
private int getCurrentlyShowingValue() {
int currentIndex = getCurrentItemShowing();
if (currentIndex == HOUR_INDEX) {
return mCurrentHoursOfDay;
} else if (currentIndex == MINUTE_INDEX) {
return mCurrentMinutes;
} else {
return -1;
}
}
public int getIsCurrentlyAmOrPm() {
if (mCurrentHoursOfDay < 12) {
return HALF_DAY_1;
} else if (mCurrentHoursOfDay < 24) {
return HALF_DAY_2;
}
return -1;
}
/**
* Set the internal as either AM or PM.
*/
// TODO: Rename to setHalfDay
public void setAmOrPm(int amOrPm) {
final int initialHalfDay = getIsCurrentlyAmOrPm();
setValueForItem(AMPM_INDEX, amOrPm);
if (amOrPm != initialHalfDay
&& mIs24HourMode
// && getCurrentItemShowing() == HOUR_INDEX
&& m24HoursGrid != null) {
m24HoursGrid.swapTexts();
mListener.onValueSelected(HOUR_INDEX, mCurrentHoursOfDay, false);
}
}
/**
* Set the internal value for the hour, minute, or AM/PM.
*/
private void setValueForItem(int index, int value) {
// Log.d(TAG, String.format("setValueForItem(%d, %d)", index, value));
if (index == HOUR_INDEX) {
mCurrentHoursOfDay = value;
setHourGridSelection(value);
} else if (index == MINUTE_INDEX){
mCurrentMinutes = value;
mMinutesGrid.setSelection(value);
} else if (index == AMPM_INDEX) {
if (value == HALF_DAY_1) {
mCurrentHoursOfDay = mCurrentHoursOfDay % 12;
} else if (value == HALF_DAY_2) {
mCurrentHoursOfDay = (mCurrentHoursOfDay % 12) + 12;
}
setHourGridSelection(mCurrentHoursOfDay);
}
}
private void setHourGridSelection(int value) {
if (mIs24HourMode) {
m24HoursGrid.setSelection(value);
} else {
value = value % 12;
if (value == 0) {
value = 12;
}
mHoursGrid.setSelection(value);
}
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import com.philliphsu.clock2.R;
/**
* Created by Phillip Hsu on 8/17/2016.
*/
public class HoursGrid extends NumbersGrid {
public HoursGrid(Context context) {
super(context);
}
@Override
public void setSelection(int value) {
super.setSelection(value);
// We expect value to be within [1, 12]. The position in the grid where
// value is located is thus (value - 1).
setIndicator(getChildAt(value - 1));
}
@Override
protected int contentLayout() {
return R.layout.content_hours_grid;
}
@Override
protected int indexOfDefaultValue() {
// This is the index of number 12.
return getChildCount() - 1;
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.ImageButton;
import com.philliphsu.clock2.R;
/**
* Created by Phillip Hsu on 8/17/2016.
*/
public class MinutesGrid extends NumbersGrid {
private static final String TAG = "MinutesGrid";
private final ImageButton mMinusButton;
private final ImageButton mPlusButton;
public MinutesGrid(Context context) {
super(context);
mMinusButton = (ImageButton) getChildAt(getChildCount() - 2);
mPlusButton = (ImageButton) getChildAt(getChildCount() - 1);
// We're not doing method binding because we don't have IDs set on these buttons.
mMinusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int value = getSelection() - 1;
if (value < 0)
value = 59;
setSelection(value);
mSelectionListener.onNumberSelected(value);
}
});
mPlusButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int value = getSelection() + 1;
if (value == 60)
value = 0;
setSelection(value);
mSelectionListener.onNumberSelected(value);
}
});
}
@Override
public void setSelection(int value) {
super.setSelection(value);
if (value % 5 == 0) {
// The new value is one of the predetermined minute values
int positionOfValue = value / 5;
setIndicator(getChildAt(positionOfValue));
} else {
clearIndicator();
}
}
@Override
protected int contentLayout() {
return R.layout.content_minutes_grid;
}
@Override
void setTheme(Context context, boolean themeDark) {
super.setTheme(context, themeDark);
if (themeDark) {
// Resources default to dark-themed color (#FFFFFF)
// If vector fill color is transparent, programmatically tinting will not work.
// Since dark-themed active icon color is fully opaque, use that color as the
// base color and tint at runtime as needed.
mMinusButton.setImageResource(R.drawable.ic_minus_circle_24dp);
mPlusButton.setImageResource(R.drawable.ic_add_circle_24dp);
} else {
// Tint drawables
final int colorActiveLight = ContextCompat.getColor(context, R.color.icon_color_active_light);
mMinusButton.setImageDrawable(Utils.getTintedDrawable(
context, R.drawable.ic_minus_circle_24dp, colorActiveLight));
mPlusButton.setImageDrawable(Utils.getTintedDrawable(
context, R.drawable.ic_add_circle_24dp, colorActiveLight));
}
}
}

View File

@ -1,195 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayout;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.philliphsu.clock2.R;
/**
* Created by Phillip Hsu on 8/17/2016.
*/
public abstract class NumbersGrid extends GridLayout implements View.OnClickListener {
private static final String TAG = "NumbersGrid";
private static final int COLUMN_COUNT = 3;
// Package visible so our concrete subclasses (in the same package) can access this.
OnNumberSelectedListener mSelectionListener;
View mLastSelectedView;
final int mSelectedTextColor;
// TODO: The half-day buttons in the dialog's layout also need to use this color.
// Consider moving this to either the Dialog class, or move the buttons and the FAB
// to the GridSelectorLayout class and then move these to GridSelectorLayout.
int mDefaultTextColor;
private boolean mIsInitialized;
private int mSelection; // The number selected from this grid
public interface OnNumberSelectedListener {
void onNumberSelected(int number);
}
@LayoutRes
protected abstract int contentLayout();
public NumbersGrid(Context context) {
super(context);
setColumnCount(COLUMN_COUNT);
inflate(context, contentLayout(), this);
// We don't do method binding because we don't know the IDs of
// our subclasses' buttons, if any.
registerClickListeners();
mIsInitialized = false;
mDefaultTextColor = ContextCompat.getColor(context, R.color.text_color_primary_light);
// The reason we can use the Context passed here and get the correct accent color
// is that this NumbersGrid is programmatically created by the GridSelectorLayout in
// its initialize(), and the Context passed in there is from
// NumberGridTimePickerDialog.getActivity().
mSelectedTextColor = Utils.getThemeAccentColor(context);
final View defaultSelectedView = getChildAt(indexOfDefaultValue());
mSelection = valueOf(defaultSelectedView);
setIndicator(defaultSelectedView);
}
public void initialize(OnNumberSelectedListener listener) {
if (mIsInitialized) {
Log.e(TAG, "This NumbersGrid may only be initialized once.");
return;
}
mSelectionListener = listener;
mIsInitialized = true;
}
public int getSelection() {
return mSelection;
}
public void setSelection(int value) {
mSelection = value;
}
/**
* The default implementation assumes the clicked view is of type TextView,
* casts the view accordingly, and parses the number from the text it contains.
* @param v the View that was clicked
*/
@Override
public void onClick(View v) {
setIndicator(v);
mSelection = valueOf(v);
mSelectionListener.onNumberSelected(mSelection);
}
/**
* Returns whether the specified View from our hierarchy can have an
* OnClickListener registered on it. The default implementation
* checks if this view is of type TextView. Subclasses can override
* this to fit their own criteria of what types of Views in their
* hierarchy can have a click listener registered on.
*
* @param view a child view from our hierarchy
*/
protected boolean canRegisterClickListener(View view) {
return view instanceof TextView;
}
/**
* Sets a selection indicator on the clicked number button. The indicator
* is the accent color applied to the button's text.
*
* @param view the clicked number button
*/
protected void setIndicator(View view) {
clearIndicator(); // Does nothing if there was no indicator last selected
TextView tv = (TextView) view;
tv.setTextColor(mSelectedTextColor);
mLastSelectedView = view;
}
/**
* Clear the selection indicator on the last selected view. Clearing the indicator
* reverts the text color back to its default.
*/
protected void clearIndicator() {
if (mLastSelectedView != null) {
TextView tv = (TextView) mLastSelectedView;
tv.setTextColor(mDefaultTextColor);
mLastSelectedView = null;
}
}
/**
* @return the index for the number button that should have the indicator set on by default.
* The base implementation returns 0, for the first child.
*/
protected int indexOfDefaultValue() {
return 0;
}
/**
* @return the number held by the button parsed into an integer. The base implementation
* assumes the view is of type TextView.
*/
protected int valueOf(View button) {
return Integer.parseInt(((TextView) button).getText().toString());
}
/**
* The default implementation sets the appropriate text color on all of the number buttons
* as determined by {@link #canRegisterClickListener(View)}.
*/
void setTheme(Context context, boolean themeDark) {
mDefaultTextColor = ContextCompat.getColor(context, themeDark?
R.color.text_color_primary_dark : R.color.text_color_primary_light);
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
// TODO: We can move this to the ctor, because this isn't dependent on the theme.
// The only issue is we would have to write another for loop iterating through all
// the buttons... but that is just prematurely worrying about optimizations..
Utils.setColorControlHighlight(v, mSelectedTextColor/*colorAccent*/);
// Filter out views that aren't number buttons
if (canRegisterClickListener(v)) {
final TextView tv = (TextView) v;
// Filter out the current selection
if (mSelection != valueOf(tv)) {
tv.setTextColor(mDefaultTextColor);
}
}
}
}
/**
* Iterates through our hierarchy and sets the subclass's implementation of OnClickListener
* on each number button encountered. By default, the number buttons are assumed to be of
* type TextView.
*/
private void registerClickListeners() {
int i = 0;
View v;
while (i < getChildCount() && canRegisterClickListener(v = getChildAt(i))) {
v.setOnClickListener(this);
i++;
}
}
}

View File

@ -1,617 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import com.philliphsu.clock2.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import butterknife.Bind;
import butterknife.OnClick;
import butterknife.OnLongClick;
/**
* Created by Phillip Hsu on 7/12/2016.
*/
public class NumpadTimePicker extends GridLayoutNumpad {
// 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;
// Constant for converting text digits to numeric digits in base-10.
private static final int BASE_10 = 10;
// AmPmStates
static final int UNSPECIFIED = -1;
static final int AM = 0;
static final int PM = 1;
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 {}
@AmPmState
private int mAmPmState = UNSPECIFIED;
private final StringBuilder mFormattedInput = new StringBuilder(MAX_CHARS);
@Bind({ R.id.leftAlt, R.id.rightAlt })
Button[] mAltButtons;
@Bind(R.id.fab) FloatingActionButton mFab;
@Bind(R.id.backspace) ImageButton mBackspace;
private boolean mThemeDark;
private int mFabDisabledColorDark;
private int mFabDisabledColorLight;
@Nullable
private final ObjectAnimator mElevationAnimator;
/**
* Provides additional APIs to configure clients' display output.
*/
public interface OnInputChangeListener extends GridLayoutNumpad.OnInputChangeListener {
/**
* Called when this numpad's buttons are all disabled, indicating no further
* digits can be inserted.
*/
void onInputDisabled();
}
public NumpadTimePicker(Context context) {
this(context, null);
}
public NumpadTimePicker(Context context, AttributeSet attrs) {
super(context, attrs);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mElevationAnimator = ObjectAnimator.ofFloat(mFab, "elevation",
getResources().getDimension(R.dimen.fab_elevation))
.setDuration(200);
mElevationAnimator.setInterpolator(new DecelerateInterpolator());
} else {
// Only animate the elevation for 21+ because changing elevation on pre-21
// shifts the FAB slightly up/down. For that reason, pre-21 has elevation
// permanently set to 0 (in XML).
mElevationAnimator = null;
}
init();
}
@Override
void setTheme(Context context, boolean themeDark) {
super.setTheme(context, themeDark);
mThemeDark = themeDark;
// this.getContext() ==> default teal accent color
// application context ==> white
// The Context that was passed in is NumpadTimePickerDialog.getContext() which
// is probably the host Activity. I have no idea what this.getContext() returns,
// but its probably some internal type that isn't tied to any of our application
// components.
// So, we kept the 0-9 buttons as TextViews, but here we kept
// the alt buttons as actual Buttons...
for (Button b : mAltButtons) {
setTextColor(b);
Utils.setColorControlHighlight(b, mAccentColor);
}
Utils.setColorControlHighlight(mBackspace, mAccentColor);
ColorStateList colorBackspace = ContextCompat.getColorStateList(context,
themeDark? R.color.icon_color_dark : R.color.icon_color);
Utils.setTintList(mBackspace, mBackspace.getDrawable(), colorBackspace);
ColorStateList colorIcon = ContextCompat.getColorStateList(context,
themeDark? R.color.icon_color_dark : R.color.fab_icon_color);
Utils.setTintList(mFab, mFab.getDrawable(), colorIcon);
// Make sure the dark theme disabled color shows up initially
updateFabState();
}
@Override
public int capacity() {
return MAX_DIGITS;
}
@Override
protected int contentLayout() {
return R.layout.content_numpad_time_picker;
}
@Override
protected void enable(int lowerLimitInclusive, int upperLimitExclusive) {
super.enable(lowerLimitInclusive, upperLimitExclusive);
if (lowerLimitInclusive == 0 && upperLimitExclusive == 0) {
// For 12-hour clock, alt buttons need to be disabled as well before firing onInputDisabled()
if (!is24HourFormat() && (mAltButtons[0].isEnabled() || mAltButtons[1].isEnabled())) {
return;
}
((OnInputChangeListener) getOnInputChangeListener()).onInputDisabled();
}
}
@Override
protected void onDigitInserted(String newDigit) {
// Append the new digit(s) to the formatter
updateFormattedInputOnDigitInserted(newDigit);
super.onDigitInserted(mFormattedInput.toString());
updateNumpadStates();
}
@Override
protected void onDigitDeleted(String newStr) {
updateFormattedInputOnDigitDeleted();
super.onDigitDeleted(mFormattedInput.toString());
updateNumpadStates();
}
@Override
protected void onDigitsCleared() {
mFormattedInput.delete(0, mFormattedInput.length());
mAmPmState = UNSPECIFIED;
updateNumpadStates(); // TOneverDO: before resetting mAmPmState to UNSPECIFIED
super.onDigitsCleared();
}
@Override
@OnClick(R.id.backspace)
public void delete() {
int len = mFormattedInput.length();
if (!is24HourFormat() && mAmPmState != UNSPECIFIED) {
mAmPmState = UNSPECIFIED;
// Delete starting from index of space to end
mFormattedInput.delete(mFormattedInput.indexOf(" "), len);
// No digit was actually deleted, but we have to notify the
// listener to update its output.
super/*TOneverDO: remove super*/.onDigitDeleted(mFormattedInput.toString());
// We also have to manually update the numpad.
updateNumpadStates();
} else {
super.delete();
}
}
@Override
@OnLongClick(R.id.backspace)
public boolean clear() {
return super.clear();
}
/** Returns the hour of day (0-23) regardless of clock system */
public int getHour() {
if (!checkTimeValid())
throw new IllegalStateException("Cannot call hourOfDay() 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 getMinute() {
if (!checkTimeValid())
throw new IllegalStateException("Cannot call minute() 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() {
// While the test looks bare, it is actually comprehensive.
// mAmPmState will remain UNSPECIFIED until a legal
// sequence of digits is inputted, no matter the clock system in use.
// TODO: So if that's the case, do we actually need 'count() < 3' here? Or better yet,
// can we simplify the code to just 'return mAmPmState != UNSPECIFIED'?
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;
}
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;
}
/*
// Convert the hour and minutes into text form, so that
// we can read each digit individually.
// 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 textDigits = is24HourFormat()
? String.format("%02d", hours)
: String.valueOf(hours);
textDigits += String.format("%02d", minutes);
int[] digits = new int[textDigits.length()];
for (int i = 0; i < textDigits.length(); i++) {
digits[i] = Character.digit(textDigits.charAt(i), BASE_10);
}
insertDigits(digits);
*/
if (is24HourFormat() || hours > 9) {
insertDigits(hours / 10, hours % 10, minutes / 10, minutes % 10);
} else {
insertDigits(hours, minutes / 10, minutes % 10);
}
mAmPmState = amPmState;
if (mAmPmState != HRS_24) {
onAltButtonClick(mAmPmState == AM ? mAltButtons[0] : mAltButtons[1]);
}
}
public String getTime() {
return mFormattedInput.toString();
}
@AmPmState
int getAmPmState() {
return mAmPmState;
}
// Because the annotation and its associated enum constants are marked private, the only real
// use for this method is to restore state across rotation after saving the value from
// #getAmPmState(). We can't directly pass in one of those accepted constants.
void setAmPmState(@AmPmState int amPmState) {
// mAmPmState = amPmState;
switch (amPmState) {
case AM:
case PM:
// mAmPmState is set for us
onAltButtonClick(mAltButtons[amPmState]);
break;
case HRS_24:
// Restoring the digits, if they make a valid time, should have already
// restored the mAmPmState to this value for us. If they don't make a
// valid time, then we refrain from setting it.
break;
case UNSPECIFIED:
// We should already be set to this value initially, but it can't hurt?
mAmPmState = amPmState;
break;
}
}
private void init() {
mFabDisabledColorDark = ContextCompat.getColor(getContext(), R.color.fab_disabled_dark);
mFabDisabledColorLight = ContextCompat.getColor(getContext(), R.color.fab_disabled_light);
// TODO: We should have the user pass in is24HourMode when they create an instance of the dialog.
if (DateFormat.is24HourFormat(getContext())) {
mAltButtons[0].setText(R.string.left_alt_24hr);
mAltButtons[1].setText(R.string.right_alt_24hr);
} else {
String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
mAltButtons[0].setText(amPmTexts[Calendar.AM]);
mAltButtons[1].setText(amPmTexts[Calendar.PM]);
}
updateNumpadStates();
}
@OnClick({ R.id.leftAlt, R.id.rightAlt })
void onAltButtonClick(TextView altBtn) {
// Manually insert special characters for 12-hour clock
if (!is24HourFormat()) {
if (count() <= 2) {
// The colon is inserted for you
insertDigits(0, 0);
}
// text is AM or PM, so include space before
String ampm = altBtn.getText().toString();
mFormattedInput.append(' ').append(ampm);
String am = new DateFormatSymbols().getAmPmStrings()[0];
mAmPmState = ampm.equals(am) ? AM : PM;
// Digits will be shown for you on insert, but not AM/PM
super/*TOneverDO: remove super*/.onDigitInserted(mFormattedInput.toString());
} else {
CharSequence text = altBtn.getText();
int[] digits = new int[text.length() - 1];
// charAt(0) is the colon, so skip i = 0.
// We are only interested in storing the digits.
for (int i = 1; i < text.length(); i++) {
// The array and the text do not have the same lengths,
// so the iterator value does not correspond to the
// array index directly
digits[i - 1] = Character.digit(text.charAt(i), BASE_10);
}
// Colon is added for you
insertDigits(digits);
mAmPmState = HRS_24;
}
updateNumpadStates();
}
private boolean is24HourFormat() {
return DateFormat.is24HourFormat(getContext());
}
private void updateFormattedInputOnDigitInserted(String newDigits) {
mFormattedInput.append(newDigits);
// 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) {
int colonAt = mFormattedInput.indexOf(":");
// Since we now batch update the formatted input whenever
// digits are inserted, the colon may legitimately not be
// present in the formatted input when this is initialized.
if (colonAt != -1) {
// Colon needs to move, so remove the colon previously added
mFormattedInput.deleteCharAt(colonAt);
}
mFormattedInput.insert(2, ':');
// Time is legal in 24-hour clock
if (is24HourFormat()) {
mAmPmState = HRS_24;
}
}
}
private void updateFormattedInputOnDigitDeleted() {
int len = mFormattedInput.length();
mFormattedInput.delete(len - 1, len);
if (count() == 3) {
int value = getInput();
// 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.
if (value <= 155 || value >= 200 && value <= 235) {
mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":"));
mFormattedInput.insert(1, ":");
} else {
// previously [16:00, 19:59]
mAmPmState = UNSPECIFIED;
}
} else if (count() == 2) {
// Remove the colon
mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":"));
// No time can be valid with only 2 digits in either system.
// I don't think we actually need this, but it can't hurt?
mAmPmState = UNSPECIFIED;
}
}
private void updateNumpadStates() {
// TOneverDO: after updateNumberKeysStates(), esp. if clock is 12-hour,
// because it calls enable(0, 0), which checks if the alt buttons have been
// disabled as well before firing the onInputDisabled().
updateAltButtonStates();
updateBackspaceState();
updateNumberKeysStates();
updateFabState();
}
private void updateFabState() {
final boolean lastEnabled = mFab.isEnabled();
mFab.setEnabled(checkTimeValid());
// If the fab was last enabled and we rotate, this check will prevent us from
// restoring the color; it will instead show up opaque white with an eclipse.
// Why isn't the FAB initialized to enabled == false when it is recreated?
// The FAB class probably saves its own state.
// if (lastEnabled == mFab.isEnabled())
// return;
// Workaround for mFab.setBackgroundTintList() because I don't know how to reference the
// correct accent color in XML. Also because I don't want to programmatically create a
// ColorStateList.
int color;
if (mFab.isEnabled()) {
color = mAccentColor;
// If FAB was last enabled, then don't run the anim again.
if (mElevationAnimator != null && !lastEnabled) {
mElevationAnimator.start();
}
} else {
color = mThemeDark? mFabDisabledColorDark : mFabDisabledColorLight;
if (lastEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mElevationAnimator != null && mElevationAnimator.isRunning()) {
// Otherwise, eclipse will show.
mElevationAnimator.end();
}
// No animation, otherwise we'll see eclipsing.
mFab.setElevation(0);
}
}
// TODO: How can we animate the background color? There is a ObjectAnimator.ofArgb()
// method, but that uses color ints as values. What we'd really need is something like
// ColorStateLists as values. There is an ObjectAnimator.ofObject(), but I don't know
// how that works. There is also a ValueAnimator.ofInt(), which doesn't need a
// target object.
mFab.setBackgroundTintList(ColorStateList.valueOf(color));
}
private void updateBackspaceState() {
mBackspace.setEnabled(count() > 0);
}
private void updateAltButtonStates() {
if (count() == 0) {
// No input, no access!
mAltButtons[0].setEnabled(false);
mAltButtons[1].setEnabled(false);
} else if (count() == 1) {
// Any of 0-9 inputted, always have access in either clock.
mAltButtons[0].setEnabled(true);
mAltButtons[1].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;
mAltButtons[0].setEnabled(validTwoDigitHour);
mAltButtons[1].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.
mAltButtons[0].setEnabled(false);
mAltButtons[1].setEnabled(false);
} else {
// True for any 3 digits, if AM/PM not already entered
boolean enabled = mAmPmState == UNSPECIFIED;
mAltButtons[0].setEnabled(enabled);
mAltButtons[1].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;
mAltButtons[0].setEnabled(enabled);
mAltButtons[1].setEnabled(enabled);
}
}
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);
}
}
}
}
}
}

View File

@ -1,212 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.util.TimeTextUtils;
import butterknife.Bind;
import butterknife.OnClick;
/**
* Created by Phillip Hsu on 7/12/2016.
*
*/
// TODO: Use library's NumberPadTimePickerDialog instead.
@Deprecated
public class NumpadTimePickerDialog extends BaseTimePickerDialog
implements NumpadTimePicker.OnInputChangeListener {
private static final String TAG = "NumpadTimePickerDialog";
private static final String KEY_IS_24_HOUR_VIEW = "is_24_hour_view";
private static final String KEY_DIGITS_INPUTTED = "digits_inputted";
private static final String KEY_AMPM_STATE = "ampm_state";
private static final String KEY_THEME_DARK = "theme_dark";
private static final String KEY_THEME_SET_AT_RUNTIME = "theme_set_at_runtime";
private boolean mIs24HourMode; // TODO: Why do we need this?
/**
* The digits stored in the numpad from the last time onSaveInstanceState() was called.
*
* Why not have the NumpadTimePicker class save state itself? Because it's a lot more
* code to do so, as you have to create your own SavedState subclass. Also, we modeled
* this dialog class on the RadialTimePickerDialog, where the RadialPickerLayout also
* depends on the dialog to save its state.
*/
private int[] mInputtedDigits;
private int mAmPmState = NumpadTimePicker.UNSPECIFIED; // TOneverDO: zero initial value, b/c 0 == AM
private boolean mThemeDark;
private boolean mThemeSetAtRuntime;
// Don't need to keep a reference to the dismiss ImageButton
@Bind(R.id.input_time) TextView mInputField;
@Bind(R.id.number_grid) NumpadTimePicker mNumpad;
// @Bind(R.id.focus_grabber) View mFocusGrabber;
// TODO: We don't need to pass in an initial hour and minute for a new instance.
@Deprecated
public static NumpadTimePickerDialog newInstance(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
NumpadTimePickerDialog ret = new NumpadTimePickerDialog();
ret.initialize(callback, hourOfDay, minute, is24HourMode);
return ret;
}
// TODO: is24HourMode param
public static NumpadTimePickerDialog newInstance(OnTimeSetListener callback) {
NumpadTimePickerDialog ret = new NumpadTimePickerDialog();
// TODO: Do these in initialize()
ret.setOnTimeSetListener(callback);
ret.mThemeDark = false;
ret.mThemeSetAtRuntime = false;
return ret;
}
@Deprecated
public void initialize(OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
setOnTimeSetListener(callback);
mIs24HourMode = is24HourMode;
}
/**
* Set a dark or light theme. NOTE: this will only take effect for the next onCreateView.
*/
public void setThemeDark(boolean dark) {
mThemeDark = dark;
mThemeSetAtRuntime = true;
}
public boolean isThemeDark() {
return mThemeDark;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mInputtedDigits = savedInstanceState.getIntArray(KEY_DIGITS_INPUTTED);
mIs24HourMode = savedInstanceState.getBoolean(KEY_IS_24_HOUR_VIEW);
mAmPmState = savedInstanceState.getInt(KEY_AMPM_STATE);
mThemeDark = savedInstanceState.getBoolean(KEY_THEME_DARK);
mThemeSetAtRuntime = savedInstanceState.getBoolean(KEY_THEME_SET_AT_RUNTIME);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (!mThemeSetAtRuntime) {
mThemeDark = Utils.isDarkTheme(getActivity(), mThemeDark);
}
mNumpad.setOnInputChangeListener(this);
mNumpad.insertDigits(mInputtedDigits); // TOneverDO: before mNumpad.setOnInputChangeListener(this);
mNumpad.setAmPmState(mAmPmState);
// Show the cursor immediately
// mInputField.requestFocus();
//updateInputText(""); // Primarily to disable 'OK'
// Prepare colors
int accentColor = Utils.getThemeAccentColor(getContext());
int lightGray = ContextCompat.getColor(getContext(), R.color.light_gray);
int darkGray = ContextCompat.getColor(getContext(), R.color.dark_gray);
int white = ContextCompat.getColor(getContext(), android.R.color.white);
// Set background color of entire view
view.setBackgroundColor(mThemeDark? darkGray : white);
TextView inputTime = (TextView) view.findViewById(R.id.input_time);
inputTime.setBackgroundColor(mThemeDark? lightGray : accentColor);
inputTime.setTextColor(ContextCompat.getColor(getContext(), android.R.color.white));
mNumpad.setTheme(getContext()/*DO NOT GIVE THE APPLICATION CONTEXT, OR ELSE THE NUMPAD
CAN'T GET THE CORRECT ACCENT COLOR*/, mThemeDark);
return view;
}
@Override
protected int contentLayout() {
return 0;
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mNumpad != null) {
outState.putIntArray(KEY_DIGITS_INPUTTED, mNumpad.getDigits());
outState.putBoolean(KEY_IS_24_HOUR_VIEW, mIs24HourMode);
outState.putInt(KEY_AMPM_STATE, mNumpad.getAmPmState());
outState.putBoolean(KEY_THEME_DARK, mThemeDark);
outState.putBoolean(KEY_THEME_SET_AT_RUNTIME, mThemeSetAtRuntime);
}
}
@Override
public void onDigitInserted(String newStr) {
updateInputText(newStr);
}
@Override
public void onDigitDeleted(String newStr) {
updateInputText(newStr);
}
@Override
public void onDigitsCleared() {
updateInputText("");
}
@Override
public void onInputDisabled() {
// Steals the focus from the EditText
// mFocusGrabber.requestFocus();
}
// @OnTouch(R.id.input_time)
// boolean captureTouchOnEditText() {
// // Capture touch events on the EditText field, because we want it to do nothing.
// return true;
// }
// The FAB is not defined directly in this dialog's layout, but rather in the layout
// of the NumpadTimePicker. We can always reference a child of a ViewGroup that is
// part of our layout.
@OnClick(R.id.fab)
void confirmSelection() {
if (!mNumpad.checkTimeValid())
return;
onTimeSet(mNumpad, mNumpad.getHour(), mNumpad.getMinute());
}
private void updateInputText(String inputText) {
TimeTextUtils.setText(inputText, mInputField);
// // Move the cursor
// mInputField.setSelection(mInputField.length());
// if (mFocusGrabber.isFocused()) {
// // Return focus to the EditText
// mInputField.requestFocus();
// }
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.util.ConfigurationUtils;
import butterknife.Bind;
import butterknife.ButterKnife;
/**
* Created by Phillip Hsu on 7/21/2016.
*/
public class TwentyFourHourGridItem extends LinearLayout {
@Bind(R.id.primary) TextView mPrimaryText;
@Bind(R.id.secondary) TextView mSecondaryText;
public TwentyFourHourGridItem(Context context) {
super(context);
init();
}
public TwentyFourHourGridItem(Context context, AttributeSet attrs) {
super(context, attrs);
init();
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.TwentyFourHourGridItem, 0, 0);
try {
setPrimaryText(a.getString(R.styleable.TwentyFourHourGridItem_primaryText));
setSecondaryText(a.getString(R.styleable.TwentyFourHourGridItem_secondaryText));
} finally {
a.recycle();
}
}
public CharSequence getPrimaryText() {
return mPrimaryText.getText();
}
public void setPrimaryText(CharSequence text) {
mPrimaryText.setText(text);
}
public CharSequence getSecondaryText() {
return mSecondaryText.getText();
}
public void setSecondaryText(CharSequence text) {
mSecondaryText.setText(text);
}
public void swapTexts() {
CharSequence primary = mPrimaryText.getText();
setPrimaryText(mSecondaryText.getText());
setSecondaryText(primary);
}
public TextView getPrimaryTextView() {
return (TextView) getChildAt(0);
}
public TextView getSecondaryTextView() {
return (TextView) getChildAt(1);
}
private void init() {
// TODO: Why isn't ALT-ENTER giving us an option to static import this method?
final int orientation = ConfigurationUtils.getOrientation(getResources());
setOrientation(orientation == Configuration.ORIENTATION_PORTRAIT ?
VERTICAL : /*LANDSCAPE*/HORIZONTAL);
setGravity(Gravity.CENTER);
inflate(getContext(), R.layout.content_24h_grid_item, this);
ButterKnife.bind(this);
}
}

View File

@ -1,117 +0,0 @@
/*
* Copyright (C) 2016 Phillip Hsu
*
* 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.philliphsu.clock2.timepickers;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.view.View;
import com.philliphsu.clock2.R;
/**
* Created by Phillip Hsu on 8/17/2016.
*/
public class TwentyFourHoursGrid extends NumbersGrid implements View.OnLongClickListener {
private static final String TAG = "TwentyFourHoursGrid";
private int mSecondaryTextColor;
public TwentyFourHoursGrid(Context context) {
super(context);
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).setOnLongClickListener(this);
}
mSecondaryTextColor = ContextCompat.getColor(context, R.color.text_color_secondary_light);
}
@Override
protected int contentLayout() {
return R.layout.content_24h_number_grid;
}
@Override
protected boolean canRegisterClickListener(View view) {
return view instanceof TwentyFourHourGridItem;
}
@Override
public void onClick(View v) {
final int newVal = valueOf(v);
setSelection(newVal);
mSelectionListener.onNumberSelected(newVal);
}
@Override
public boolean onLongClick(View v) {
TwentyFourHourGridItem item = (TwentyFourHourGridItem) v;
// Unfortunately, we can't use #valueOf() for this because we want the secondary value.
int newVal = Integer.parseInt(item.getSecondaryText().toString());
mSelectionListener.onNumberSelected(newVal);
// TOneverDO: Call before firing the onNumberSelected() callback, because we want the
// dialog to advance to the next index WITHOUT seeing the text swapping.
swapTexts();
// TOneverDO: Call before swapping texts, because setIndicator() uses the primary TextView.
setSelection(newVal);
return true; // Consume the long click
}
@Override
public void setSelection(int value) {
super.setSelection(value);
// The value is within [0, 23], but we have only 12 buttons.
setIndicator(getChildAt(value % 12));
}
@Override
protected void setIndicator(View view) {
TwentyFourHourGridItem item = (TwentyFourHourGridItem) view;
super.setIndicator(item.getPrimaryTextView());
}
@Override
void setTheme(Context context, boolean themeDark) {
mDefaultTextColor = ContextCompat.getColor(context, themeDark?
R.color.text_color_primary_dark : R.color.text_color_primary_light);
mSecondaryTextColor = ContextCompat.getColor(context, themeDark?
R.color.text_color_secondary_dark : R.color.text_color_secondary_light);
for (int i = 0; i < getChildCount(); i++) {
TwentyFourHourGridItem item = (TwentyFourHourGridItem) getChildAt(i);
// TODO: We could move this to the ctor, in the superclass. If so, then this class
// doesn't need to worry about setting the highlight.
Utils.setColorControlHighlight(item, mSelectedTextColor/*colorAccent*/);
// Filter out the current selection.
if (getSelection() != valueOf(item)) {
item.getPrimaryTextView().setTextColor(mDefaultTextColor);
// The indicator can only be set on the primary text, which is why we don't need
// the secondary text here.
}
item.getSecondaryTextView().setTextColor(mSecondaryTextColor);
}
}
public void swapTexts() {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
((TwentyFourHourGridItem) v).swapTexts();
}
}
@Override
protected int valueOf(View button) {
return Integer.parseInt(((TwentyFourHourGridItem) button).getPrimaryText().toString());
}
}

View File

@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013 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
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/time_display"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@android:color/white" >
<View
android:id="@+id/center_view"
android:layout_width="1dp"
android:layout_height="1dp"
android:background="#00000000"
android:layout_centerInParent="true"
android:visibility="invisible"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/hour_space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_placeholder"
android:layout_toLeftOf="@+id/separator"
android:layout_centerVertical="true"
android:visibility="invisible"
style="@style/time_label"
android:importantForAccessibility="no" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignRight="@+id/hour_space"
android:layout_alignLeft="@+id/hour_space"
android:layout_marginLeft="@dimen/extra_time_label_margin"
android:layout_marginRight="@dimen/extra_time_label_margin"
android:layout_centerVertical="true" >
<com.philliphsu.clock2.timepickers.AccessibleTextView
android:id="@+id/hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_placeholder"
android:textColor="@color/blue"
android:gravity="center_horizontal"
android:layout_gravity="center"
style="@style/time_label" />
</FrameLayout>
<TextView
android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_separator"
android:paddingLeft="@dimen/separator_padding"
android:paddingRight="@dimen/separator_padding"
android:layout_alignRight="@+id/center_view"
android:layout_centerVertical="true"
style="@style/time_label"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/minutes_space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_placeholder"
android:layout_toRightOf="@+id/separator"
android:layout_centerVertical="true"
android:visibility="invisible"
style="@style/time_label"
android:importantForAccessibility="no" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignRight="@+id/minutes_space"
android:layout_alignLeft="@+id/minutes_space"
android:layout_marginLeft="@dimen/extra_time_label_margin"
android:layout_marginRight="@dimen/extra_time_label_margin"
android:layout_centerVertical="true" >
<com.philliphsu.clock2.timepickers.AccessibleTextView
android:id="@+id/minutes"
style="@style/time_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/time_placeholder"
android:layout_gravity="center" />
</FrameLayout>
<com.philliphsu.clock2.timepickers.AccessibleTextView
android:id="@+id/ampm_hitspace"
android:layout_width="@dimen/ampm_label_size"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_alignLeft="@+id/ampm_label"
android:layout_alignRight="@+id/ampm_label" />
<TextView
android:id="@+id/ampm_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_placeholder"
android:paddingLeft="@dimen/ampm_left_padding"
android:paddingRight="@dimen/ampm_left_padding"
android:layout_toRightOf="@+id/minutes_space"
android:layout_alignBaseline="@+id/separator"
style="@style/ampm_label"
android:importantForAccessibility="no" />
</RelativeLayout>

View File

@ -1,2 +0,0 @@
configurations.maybeCreate("default")
artifacts.add("default", file('bottomsheetpickers-debug.aar'))

View File

@ -1,2 +0,0 @@
configurations.maybeCreate("default")
artifacts.add("default", file('bottomsheetpickers-release.aar'))

View File

@ -22,7 +22,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong

View File

@ -1,22 +1,6 @@
#
# Copyright (C) 2016 Phillip Hsu
#
# 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.
#
#Mon Dec 28 10:00:20 PST 2015
#Fri Feb 24 23:57:57 PST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

View File

@ -14,4 +14,4 @@
* limitations under the License.
*/
include ':app', ':bottomsheetpickers-release', ':bottomsheetpickers-debug'
include ':app'