Update gradle files and BottomSheetPickers library. Remove some conflicting classes and resources.
This commit is contained in:
parent
e83bd2d4a1
commit
8584488326
@ -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'
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
// }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
configurations.maybeCreate("default")
|
||||
artifacts.add("default", file('bottomsheetpickers-debug.aar'))
|
||||
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
configurations.maybeCreate("default")
|
||||
artifacts.add("default", file('bottomsheetpickers-release.aar'))
|
||||
@ -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
|
||||
|
||||
20
gradle/wrapper/gradle-wrapper.properties
vendored
20
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
||||
@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
include ':app', ':bottomsheetpickers-release', ':bottomsheetpickers-debug'
|
||||
include ':app'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user