Deleted deprecated and unused classes and files

This commit is contained in:
Phillip Hsu 2016-09-21 15:23:51 -07:00
parent f720bae5c1
commit d5c9bcaec9
48 changed files with 21 additions and 3557 deletions

View File

@ -2,10 +2,8 @@ package com.philliphsu.clock2;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import com.google.auto.value.AutoValue;
import com.philliphsu.clock2.model.JsonSerializable;
import com.philliphsu.clock2.model.ObjectWithId;
import org.json.JSONObject;
@ -22,7 +20,7 @@ import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
* Created by Phillip Hsu on 5/26/2016.
*/
@AutoValue
public abstract class Alarm extends ObjectWithId implements JsonSerializable, Parcelable {
public abstract class Alarm extends ObjectWithId implements Parcelable {
private static final int MAX_MINUTES_CAN_SNOOZE = 30;
// =================== MUTABLE =======================
@ -211,26 +209,6 @@ public abstract class Alarm extends ObjectWithId implements JsonSerializable, Pa
return !ignoreUpcomingRingTime && ringsIn() <= TimeUnit.HOURS.toMillis(hours);
}
// TODO: Rename to getIntId() so usages refer to ObjectWithId#getIntId(), then delete this method.
public int intId() {
return getIntId();
}
// TODO: Remove method signature from JsonSerializable interface.
// TODO: Remove final modifier.
// TODO: Rename to getId() so usages refer to ObjectWithId#getId(), then delete this method.
@Override
public final long id() {
return getId();
}
@Deprecated
@Override
@NonNull
public JSONObject toJsonObject() {
throw new UnsupportedOperationException();
}
// ============================ PARCELABLE ==============================
// Unfortunately, we can't use the Parcelable extension for AutoValue because
// our model isn't totally immutable. Our mutable properties will be left

View File

@ -1,100 +0,0 @@
package com.philliphsu.clock2;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.view.ViewGroup;
import java.util.List;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
public abstract class BaseAdapter<T, VH extends BaseViewHolder<T>> extends RecyclerView.Adapter<VH> {
private final OnListItemInteractionListener<T> mListener;
private final SortedList<T> mItems;
protected BaseAdapter(Class<T> cls, List<T> items, OnListItemInteractionListener<T> listener) {
mItems = new SortedList<>(cls, new SortedListAdapterCallback<T>(this) {
@Override
public int compare(T o1, T o2) {
//return BaseAdapter.this.<T, VH>*compare(o1, o2); // *: See note below
return BaseAdapter.this.compare(o1, o2);
// Only need to specify the type when calling a _generic_ method
// that defines _its own type parameters_ in its signature, but even
// then, it's not actually necessary because the compiler can
// infer the type.
// This is just an _abstract_ method that takes params of type T.
}
@Override
public boolean areContentsTheSame(T oldItem, T newItem) {
return BaseAdapter.this.areContentsTheSame(oldItem, newItem);
}
@Override
public boolean areItemsTheSame(T item1, T item2) {
return BaseAdapter.this.areItemsTheSame(item1, item2);
}
});
mListener = listener;
mItems.addAll(items);
}
protected abstract VH onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<T> listener);
protected abstract int compare(T o1, T o2);
protected abstract boolean areContentsTheSame(T oldItem, T newItem);
protected abstract boolean areItemsTheSame(T item1, T item2);
@Override // not final to allow subclasses to use the viewType if needed
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return onCreateViewHolder(parent, mListener);
}
@Override
public final void onBindViewHolder(VH holder, int position) {
holder.onBind(mItems.get(position));
}
@Override
public final int getItemCount() {
return mItems.size();
}
public final void replaceData(List<T> items) {
mItems.clear();
mItems.addAll(items);
}
protected final T getItem(int position) {
return mItems.get(position);
}
public final int addItem(T item) {
return mItems.add(item);
}
public final boolean removeItem(T item) {
return mItems.remove(item);
}
public final void updateItem(T oldItem, T newItem) {
// SortedList finds the index of an item by using its callback's compare() method.
// We can describe our current item update process is as follows:
// * An item's fields are modified
// * The changes are saved to the repository
// * A item update callback is fired to the RV
// * The RV calls its adapter's updateItem(), passing the instance of the modified item as both arguments
// (because modifying an item keeps the same instance of the item)
// * The SortedList tries to find the index of the param oldItem, but since its fields are changed,
// the search may end up failing because compare() could return the wrong index.
// A workaround is to copy construct the original item instance BEFORE you modify the fields.
// Then, oldItem should point to the copied instance.
// Alternatively, a better approach is to make items immutable.
mItems.updateItemAt(mItems.indexOf(oldItem), newItem);
}
}

View File

@ -1,26 +0,0 @@
package com.philliphsu.clock2;
import android.support.annotation.NonNull;
/**
* Created by Phillip Hsu on 4/30/2016.
*/
public interface DurationDisplayer {
/**
* Callback interface to be implemented by a parent/host of this displayer.
* The host should use this to tell its displayer to update its duration text
* via showAsText(long).
*/
/*public*/ interface OnTickListener {
// Listeners implement this to be notified of when
// they should update this displayer's text
void onTick();
}
/** Hosts of this displayer use this to attach themselves */
void setOnTickListener(@NonNull OnTickListener listener);
void startTicking(boolean resume);
void stopTicking();
void forceTick();
void showAsText(long duration);
}

View File

@ -1,266 +0,0 @@
package com.philliphsu.clock2;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.Space;
import android.widget.TableLayout;
import android.widget.TableRow;
import java.util.Arrays;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by Phillip Hsu on 6/2/2016.
*/
public abstract class Numpad extends TableLayout {
private static final String TAG = "Numpad";
private static final int NUM_COLUMNS = 3;
private static final int RADIX_10 = 10;
protected static final int UNMODIFIED = -1;
// Derived classes need to build this themselves via buildBackspace().
private ImageButton mBackspace;
private ImageButton mCollapse;
private int[] mInput;
private int mCount = 0;
private KeyListener mKeyListener;
@Bind({ R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four,
R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine })
Button[] mButtons;
public interface KeyListener {
void onNumberInput(String number);
void onCollapse();
void onBackspace(String newStr);
void onLongBackspace();
}
public Numpad(Context context) {
super(context);
init();
}
public Numpad(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void setKeyListener(@NonNull KeyListener listener) {
mKeyListener = listener;
}
protected KeyListener getKeyListener() {
return mKeyListener;
}
protected abstract int capacity();
protected final void enable(int lowerLimitInclusive, int upperLimitExclusive) {
if (lowerLimitInclusive < 0 || upperLimitExclusive > mButtons.length)
throw new IndexOutOfBoundsException("Upper limit out of range");
for (int i = 0; i < mButtons.length; i++)
mButtons[i].setEnabled(i >= lowerLimitInclusive && i < upperLimitExclusive);
}
protected final int valueAt(int index) {
checkIndexWithinBounds(index);
return mInput[index];
}
protected final int count() {
return mCount;
}
protected final int getInput() {
String currentInput = "";
for (int i : mInput)
if (i != UNMODIFIED)
currentInput += i;
return Integer.parseInt(currentInput);
}
protected final void buildBackspace(int r, int c) {
mBackspace = (ImageButton) buildButton(R.layout.numpad_backspace, r, c);
mBackspace.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
backspace();
}
});
mBackspace.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return longBackspace();
}
});
}
protected final void buildCollapse(int r, int c) {
mCollapse = (ImageButton) buildButton(R.layout.numpad_collapse, r, c);
mCollapse.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
checkKeyListenerSet();
mKeyListener.onCollapse();
}
});
}
protected final void newRow() {
TableRow newRow = new TableRow(getContext());
newRow.setLayoutParams(new TableRow.LayoutParams());
addView(newRow);
for (int i = 0; i < NUM_COLUMNS; i++) {
Space s = new Space(getContext());
setButtonLayoutParams(s);
newRow.addView(s);
}
}
protected final View buildButton(@LayoutRes int buttonRes, int r, int c) {
View button = View.inflate(getContext(), buttonRes, null);
setButtonLayoutParams(button);
replace(r, c, button);
return button;
}
@OnClick({ R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four,
R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine })
protected void onClick(Button button) {
onClick(button, mCount);
}
public void onClick(Button button, int at) {
if (mCount < mInput.length) {
checkIndexWithinBounds(at);
mInput[at] = Integer.parseInt(button.getText().toString());
mCount++;
}
}
/** Performs an artificial click on the Button with the specified digit. */
protected final void performClick(int digit) {
if (digit < 0 || digit >= mButtons.length)
throw new ArrayIndexOutOfBoundsException("No Button with digit " + digit);
if (!mButtons[digit].isEnabled())
throw new IllegalArgumentException("Button " + digit + " is disabled. " +
"Did you call AlarmNumpad.setInput(String) with an invalid time?");
onClick(mButtons[digit]);
}
/** Performs an artificial click on the Button with the specified digit. */
protected final void performClick(char charDigit) {
performClick(asDigit(charDigit));
}
protected void setInput(int... digits) {
if (digits.length != mInput.length)
throw new IllegalArgumentException("Input arrays not the same length");
for (int i = 0; i < digits.length; i++) {
if (digits[i] < 0 || digits[i] > 9)
throw new IllegalArgumentException("Element in input out of range");
if (!mButtons[i].isEnabled())
throw new IllegalStateException("Button with digit " + digits[i] + " is disabled");
mInput[i] = digits[i];
}
}
protected void backspace() {
if (mCount - 1 >= 0) {
mInput[--mCount] = UNMODIFIED;
}
}
// public to allow hosts of this numpad to modify its contents
public void backspace(int at) {
if (at < 0 || at > mInput.length /* at == mInput.length is valid */)
throw new IndexOutOfBoundsException("Cannot backspace on index " + at);
if (at - 1 >= 0) {
mInput[--at] = UNMODIFIED;
mCount--;
}
}
protected boolean longBackspace() {
Arrays.fill(mInput, UNMODIFIED);
mCount = 0;
return true;
}
protected void setBackspaceEnabled(boolean enabled) {
mBackspace.setEnabled(enabled);
}
protected final void notifyOnNumberInputListener(String number) {
checkKeyListenerSet();
mKeyListener.onNumberInput(number);
}
protected final void notifyOnBackspaceListener(String newStr) {
checkKeyListenerSet();
mKeyListener.onBackspace(newStr);
}
protected final void notifyOnLongBackspaceListener() {
checkKeyListenerSet();
mKeyListener.onLongBackspace();
}
private void setButtonLayoutParams(View target) {
target.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.MATCH_PARENT, 1));
}
private void replace(int r, int c, View newView) {
checkLocation(r, c);
TableRow row = (TableRow) getChildAt(r);
row.removeViewAt(c);
row.addView(newView, c);
}
// Checks if the specified location in the View hierarchy exists.
private void checkLocation(int r, int c) {
if (r < 0 || r >= getChildCount())
throw new IndexOutOfBoundsException("No TableRow at row " + r);
if (c < 0 || c >= NUM_COLUMNS)
throw new IndexOutOfBoundsException("No column " + c + " at row " + r);
}
private void checkIndexWithinBounds(int i) {
if (i < 0 || i >= mInput.length) {
throw new ArrayIndexOutOfBoundsException("Index " + i + "out of bounds");
}
}
private void checkKeyListenerSet() {
if (null == mKeyListener)
throw new NullPointerException("Numpad Key listener not set");
}
private int asDigit(char charDigit) {
if (!Character.isDigit(charDigit))
throw new IllegalArgumentException("Character is not a digit");
return Character.digit(charDigit, RADIX_10);
}
private void init() {
View.inflate(getContext(), R.layout.content_numpad, this);
ButterKnife.bind(this);
if (capacity() < 0)
throw new IllegalArgumentException("Negative capacity");
mInput = new int[capacity()];
Arrays.fill(mInput, UNMODIFIED);
}
}

View File

@ -2,14 +2,6 @@ package com.philliphsu.clock2;
/**
* Created by Phillip Hsu on 5/31/2016.
* This interface MUST be extended by Fragments that display a RecyclerView as a list.
* The reason for this is Fragments need to do an instanceof check on their host Context
* to see if it implements this interface, and instanceof cannot be used with generic type
* parameters. Why not just define this interface as a member of the Fragment class?
* Because the Fragment's BaseAdapter needs a reference to this interface, and we don't want
* to couple the BaseAdapter
* to the Fragment. By keeping this interface as generic as possible, the BaseAdapter can
* be easily adapted to not just Fragments, but also custom Views, Activities, etc.
*/
public interface OnListItemInteractionListener<T> {
void onListItemClick(T item, int position);

View File

@ -1,12 +0,0 @@
package com.philliphsu.clock2;
import android.support.annotation.StringRes;
/**
* Created by Phillip Hsu on 6/6/2016.
*/
@Deprecated
public interface SharedPreferencesHelper {
/** Suitable for retrieving the value of a ListPreference */
int getInt(@StringRes int key, int defaultValue);
}

View File

@ -1,71 +0,0 @@
package com.philliphsu.clock2;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import com.philliphsu.clock2.DurationDisplayer.OnTickListener;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
/**
* Created by Phillip Hsu on 4/18/2016.
*/
public class TickHandler extends Handler {
private static final int MSG_TICK = 0;
private static final int MSG_FORCE_TICK = 1;
@NonNull private final OnTickListener mOnTickListener;
private final long mTickInterval;
public TickHandler(@NonNull OnTickListener listener, long tickInterval) {
super(Looper.getMainLooper());
mOnTickListener = checkNotNull(listener);
mTickInterval = tickInterval;
}
@Override
public void handleMessage(Message msg) {
// Account for the time showDuration() takes to execute
// and subtract that off from the countdown interval
// (so the countdown doesn't get postponed by however long
// showDuration() takes)
long startOnTick = SystemClock.elapsedRealtime();
mOnTickListener.onTick();
long countdownRemainder = mTickInterval -
(SystemClock.elapsedRealtime() - startOnTick);
// special case: onTick took longer than countdown
// interval to complete, skip to next interval
while (countdownRemainder < 0)
countdownRemainder += mTickInterval;
if (msg.what == MSG_TICK) { // as opposed to MSG_FORCE_TICK
sendMessageDelayed(obtainMessage(MSG_TICK), mTickInterval);
}
}
public void startTicking(boolean resume) {
if (hasMessages(MSG_TICK))
return;
if (resume) {
sendMessage(obtainMessage(MSG_TICK));
} else {
sendMessageDelayed(obtainMessage(MSG_TICK), mTickInterval);
}
}
public void stopTicking() {
removeMessages(MSG_TICK);
}
/**
* Forces a single call to {@link OnTickListener#onTick() onTick()}
* without scheduling looped messages on this handler.
*/
public void forceTick() {
sendMessage(obtainMessage(MSG_FORCE_TICK));
}
}

View File

@ -13,7 +13,7 @@ import com.philliphsu.clock2.util.AlarmController;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.TimeFormatUtils.formatTime;
// TODO: Consider registering this locally instead of in the manifest.
public class UpcomingAlarmReceiver extends BroadcastReceiver {

View File

@ -12,7 +12,7 @@ import com.philliphsu.clock2.R;
import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.ringtone.RingtoneService;
import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.DateFormatUtils;
import com.philliphsu.clock2.util.TimeFormatUtils;
public class AlarmActivity extends RingtoneActivity<Alarm> {
private static final String TAG = "AlarmActivity";
@ -117,7 +117,7 @@ public class AlarmActivity extends RingtoneActivity<Alarm> {
}
private void postMissedAlarmNote() {
String alarmTime = DateFormatUtils.formatTime(this,
String alarmTime = TimeFormatUtils.formatTime(this,
getRingingObject().hour(), getRingingObject().minutes());
Notification note = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.missed_alarm))

View File

@ -35,8 +35,9 @@ import java.util.Locale;
* Created by Phillip Hsu on 4/30/2016.
*
* A modified version of the framework's Chronometer class that counts down toward the base time,
* uses the {@link System#currentTimeMillis()} timebase, and only has minute precision. It displays
* the current timer value in the form "In H hrs and M mins". // TODO: This isn't the actual format. Verify what that is.
* uses the {@link System#currentTimeMillis()} timebase, and only has minute precision.
*
* TODO: Refactor by using the ChronometerDelegate class.
*/
public class AlarmCountdown extends TextView {
private static final String TAG = "AlarmCountdown";

View File

@ -12,7 +12,7 @@ import com.philliphsu.clock2.ringtone.RingtoneService;
import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.AlarmUtils;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.TimeFormatUtils.formatTime;
public class AlarmRingtoneService extends RingtoneService<Alarm> {
private static final String TAG = "AlarmRingtoneService";

View File

@ -1,50 +0,0 @@
package com.philliphsu.clock2.alarms;
import android.view.ViewGroup;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.BaseAdapter;
import com.philliphsu.clock2.OnListItemInteractionListener;
import java.util.Arrays;
import java.util.List;
@Deprecated
public class AlarmsAdapter extends BaseAdapter<Alarm, BaseAlarmViewHolder> {
public AlarmsAdapter(List<Alarm> alarms, OnListItemInteractionListener<Alarm> listener) {
super(Alarm.class, alarms, listener);
setHasStableIds(true);
}
@Override
public long getItemId(int position) {
return getItem(position).id();
}
@Override
public BaseAlarmViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener) {
return new CollapsedAlarmViewHolder(parent, listener);
}
@Override
public int compare(Alarm o1, Alarm o2) {
return Long.compare(o1.ringsAt(), o2.ringsAt());
}
@Override
public boolean areContentsTheSame(Alarm oldItem, Alarm newItem) {
return oldItem.hour() == newItem.hour()
&& oldItem.minutes() == newItem.minutes()
&& oldItem.isEnabled() == newItem.isEnabled()
&& oldItem.label().equals(newItem.label())
&& oldItem.ringsIn() == newItem.ringsIn()
&& Arrays.equals(oldItem.recurringDays(), newItem.recurringDays())
&& oldItem.snoozingUntil() == newItem.snoozingUntil();
}
@Override
public boolean areItemsTheSame(Alarm item1, Alarm item2) {
return item1.id() == item2.id();
}
}

View File

@ -25,7 +25,7 @@ import com.philliphsu.clock2.R;
import com.philliphsu.clock2.TimePickerDialogController;
import com.philliphsu.clock2.aospdatetimepicker.Utils;
import com.philliphsu.clock2.editalarm.BaseTimePickerDialog.OnTimeSetListener;
import com.philliphsu.clock2.editalarm.TimeTextUtils;
import com.philliphsu.clock2.util.TimeTextUtils;
import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.AlarmUtils;
import com.philliphsu.clock2.util.FragmentTagUtils;
@ -39,7 +39,7 @@ import butterknife.OnTouch;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.TimeFormatUtils.formatTime;
/**
* Created by Phillip Hsu on 7/31/2016.

View File

@ -23,11 +23,6 @@ public class CollapsedAlarmViewHolder extends BaseAlarmViewHolder {
@Bind(R.id.countdown) AlarmCountdown mCountdown;
@Bind(R.id.recurring_days) TextView mDays; // TODO: use `new DateFormatSymbols().getShortWeekdays()` to set texts
@Deprecated // TODO: Delete this, the only usage is from AlarmsAdapter (SortedList), which is not used anymore.
public CollapsedAlarmViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener) {
super(parent, R.layout.item_collapsed_alarm, listener, null);
}
public CollapsedAlarmViewHolder(ViewGroup parent, OnListItemInteractionListener<Alarm> listener,
AlarmController alarmController) {
super(parent, R.layout.item_collapsed_alarm, listener, alarmController);

View File

@ -1,37 +0,0 @@
package com.philliphsu.clock2.alarms.dummy;
import com.philliphsu.clock2.Alarm;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
* <p/>
* TODO: Replace all uses of this class before publishing your app.
*/
public class DummyContent {
/**
* An array of sample (dummy) items.
*/
public static final List<Alarm> ITEMS = new ArrayList<>();
private static final int COUNT = 10;
static {
// Add some sample items.
for (int i = 1; i <= COUNT; i++) {
addItem(createAlarm(i));
}
}
private static void addItem(Alarm item) {
ITEMS.add(item);
}
private static Alarm createAlarm(int position) {
return null;
}
}

View File

@ -1,22 +0,0 @@
package com.philliphsu.clock2.editalarm;
import java.util.Date;
/**
* Created by Phillip Hsu on 6/2/2016.
*/
@Deprecated
interface AlarmContract {
interface View {
void showLabel(String label);
void showEnabled(boolean enabled);
void showCanDismissNow();
void showSnoozed(Date snoozingUntilMillis);
}
interface Presenter {
void dismissNow();
void stopSnoozing();
}
}

View File

@ -1,45 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
/**
* Couldn't figure out how to draw the numpad over the system keyboard, so
* the callback interface provided here is of no use to us.
*/
@Deprecated
public class AlarmEditText extends android.support.v7.widget.AppCompatEditText {
private OnBackPressListener mOnBackPressListener;
public AlarmEditText(Context context) {
super(context);
}
public AlarmEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlarmEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
&& event.getAction() == KeyEvent.ACTION_UP
&& mOnBackPressListener != null) {
mOnBackPressListener.onBackPress();
}
return super.dispatchKeyEvent(event); // See http://stackoverflow.com/a/5993196/5055032
}
public void setOnBackPressListener(OnBackPressListener onBackPressListener) {
mOnBackPressListener = onBackPressListener;
}
public interface OnBackPressListener {
void onBackPress();
}
}

View File

@ -1,25 +0,0 @@
package com.philliphsu.clock2.editalarm;
/**
* Created by Phillip Hsu on 6/2/2016.
*
* TODO: Consider NOT extending from AlarmContract. Instead, define the methods
* specific to this interface. Make a base implementation class of the base
* AlarmContract. When you create an implementation class of the more specific
* interface, all you need to do is implement the more specific interface.
* The base impl class will already implement the base interface methods.
* That way, each concrete interface impl doesn't need to implement the
* base interface methods again and again.
*/
public interface AlarmItemContract {
interface View extends AlarmContract.View {
void showTime(String time);
void showCountdown(long remainingTime);
void showRecurringDays(String recurringDays);
}
interface Presenter extends AlarmContract.Presenter {
void setEnabled(boolean enabled);
}
}

View File

@ -1,463 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.design.widget.FloatingActionButton;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import com.philliphsu.clock2.Numpad;
import com.philliphsu.clock2.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Phillip Hsu on 6/2/2016.
*/
public class AlarmNumpad extends Numpad {
private static final String TAG = "AlarmNumpad";
// Time can be represented with maximum of 4 digits
private static final int MAX_DIGITS = 4;
// Formatted time string has a maximum of 8 characters
// in the 12-hour clock, e.g 12:59 AM. Although the 24-hour
// clock should be capped at 5 characters, the difference
// is not significant enough to deal with the separate cases.
private static final int MAX_CHARS = 8;
private static final int UNSPECIFIED = -1;
private static final int AM = 0;
private static final int PM = 1;
private static final int HRS_24 = 2;
@IntDef({ UNSPECIFIED, AM, PM, HRS_24 }) // Specifies the accepted constants
@Retention(RetentionPolicy.SOURCE) // Usages do not need to be recorded in .class files
private @interface AmPmState {}
private Button leftAlt; // AM or :00
private Button rightAlt; // PM or :30
private FloatingActionButton fab;
private final StringBuilder mFormattedInput = new StringBuilder(MAX_CHARS);
@AmPmState
private int mAmPmState = UNSPECIFIED;
public AlarmNumpad(Context context) {
this(context, null);
}
public AlarmNumpad(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/** Returns the hour of day (0-23) regardless of clock system */
public int getHours() {
if (!checkTimeValid())
throw new IllegalStateException("Cannot call getHours() until legal time inputted");
int hours = count() < 4 ? valueAt(0) : valueAt(0) * 10 + valueAt(1);
if (hours == 12) {
switch (mAmPmState) {
case AM:
return 0;
case PM:
case HRS_24:
return 12;
default:
break;
}
}
// AM/PM clock needs value offset
return hours + (mAmPmState == PM ? 12 : 0);
}
public int getMinutes() {
if (!checkTimeValid())
throw new IllegalStateException("Cannot call getMinutes() until legal time inputted");
return count() < 4 ? valueAt(1) * 10 + valueAt(2) : valueAt(2) * 10 + valueAt(3);
}
/**
* Checks if the input stored so far qualifies as a valid time.
* For this to return {@code true}, the hours, minutes AND AM/PM
* state must be set.
*/
public boolean checkTimeValid() {
if (mAmPmState == UNSPECIFIED || mAmPmState == HRS_24 && count() < 3)
return false;
// AM or PM can only be set if the time was already valid previously, so we don't need
// to check for them.
return true;
}
@Override
protected int capacity() {
return MAX_DIGITS;
}
@Override
protected void onClick(Button button) {
super.onClick(button);
// Format and store the input as text
inputNumber(button.getText());
notifyOnNumberInputListener(mFormattedInput.toString());
updateNumpadStates();
}
@Override
protected void backspace() {
int len = mFormattedInput.length();
if (!is24HourFormat() && mAmPmState != UNSPECIFIED) {
mAmPmState = UNSPECIFIED;
// Delete starting from index of space to end
mFormattedInput.delete(mFormattedInput.indexOf(" "), len);
} else {
super.backspace();
mFormattedInput.delete(len - 1, len);
if (count() == 3) {
// Move the colon from its 4-digit position to its 3-digit position,
// unless doing so gives an invalid time.
// e.g. 17:55 becomes 1:75, which is invalid.
// All 3-digit times in the 12-hour clock at this point should be
// valid. The limits <=155 and (>=200 && <=235) are really only
// imposed on the 24-hour clock, and were chosen because 4-digit times
// in the 24-hour clock can only go up to 15:5[0-9] or be within the range
// [20:00, 23:59] if they are to remain valid when they become three digits.
// The is24HourFormat() check is therefore unnecessary.
int value = getInput();
if (value <= 155 || value >= 200 && value <= 235) {
mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":"));
mFormattedInput.insert(1, ":");
}
} else if (count() == 2) {
// Remove the colon
mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":"));
}
}
notifyOnBackspaceListener(mFormattedInput.toString());
updateNumpadStates();
}
@Override
protected boolean longBackspace() {
boolean consumed = super.longBackspace();
mFormattedInput.delete(0, mFormattedInput.length());
updateNumpadStates();
mAmPmState = UNSPECIFIED;
notifyOnLongBackspaceListener();
return consumed;
}
public void setTime(int hours, int minutes) {
if (hours < 0 || hours > 23)
throw new IllegalArgumentException("Illegal hours: " + hours);
if (minutes < 0 || minutes > 59)
throw new IllegalArgumentException("Illegal minutes: " + minutes);
// Internal representation of the time has been checked for legality.
// Now we need to format it depending on the user's clock system.
// If 12-hour clock, can't set mAmPmState yet or else this interferes
// with the button state update mechanism. Instead, cache the state
// the hour would resolve to in a local variable and set it after
// all digits are inputted.
int amPmState;
if (!is24HourFormat()) {
// Convert 24-hour times into 12-hour compatible times.
if (hours == 0) {
hours = 12;
amPmState = AM;
} else if (hours == 12) {
amPmState = PM;
} else if (hours > 12) {
hours -= 12;
amPmState = PM;
} else {
amPmState = AM;
}
} else {
amPmState = HRS_24;
}
// Only if on 24-hour clock, zero-pad single digit hours.
// Zero cannot be the first digit of any time in the 12-hour clock.
String strHrs = is24HourFormat()
? String.format("%02d", hours)
: String.valueOf(hours);
String strMins = String.format("%02d", minutes); // Zero-pad single digit minutes
// TODO: Do this off the main thread
for (int i = 0; i < strHrs.length(); i++)
performClick(strHrs.charAt(i));
for (int i = 0; i < strMins.length(); i++)
performClick(strMins.charAt(i));
mAmPmState = amPmState;
if (mAmPmState != HRS_24) {
onAltButtonClick(mAmPmState == AM ? leftAlt : rightAlt);
}
}
public String getTime() {
return mFormattedInput.toString();
}
private void updateAltButtonStates() {
if (count() == 0) {
// No input, no access!
leftAlt.setEnabled(false);
rightAlt.setEnabled(false);
} else if (count() == 1) {
// Any of 0-9 inputted, always have access in either clock.
leftAlt.setEnabled(true);
rightAlt.setEnabled(true);
} else if (count() == 2) {
// Any 2 digits that make a valid hour for either clock are eligible for access
int time = getInput();
boolean validTwoDigitHour = is24HourFormat() ? time <= 23 : time >= 10 && time <= 12;
leftAlt.setEnabled(validTwoDigitHour);
rightAlt.setEnabled(validTwoDigitHour);
} else if (count() == 3) {
if (is24HourFormat()) {
// For the 24-hour clock, no access at all because
// two more digits (00 or 30) cannot be added to 3 digits.
leftAlt.setEnabled(false);
rightAlt.setEnabled(false);
} else {
// True for any 3 digits, if AM/PM not already entered
boolean enabled = mAmPmState == UNSPECIFIED;
leftAlt.setEnabled(enabled);
rightAlt.setEnabled(enabled);
}
} else if (count() == MAX_DIGITS) {
// If all 4 digits are filled in, the 24-hour clock has absolutely
// no need for the alt buttons. However, The 12-hour clock has
// complete need of them, if not already used.
boolean enabled = !is24HourFormat() && mAmPmState == UNSPECIFIED;
leftAlt.setEnabled(enabled);
rightAlt.setEnabled(enabled);
}
}
private void updateBackspaceState() {
setBackspaceEnabled(count() > 0);
}
private void updateFabState() {
// Special case of 0 digits is legal (that is the default hint time)
if (count() == 0) {
fab.setEnabled(true);
return;
}
// Minimum of 3 digits is required
if (count() < 3) {
fab.setEnabled(false);
return;
}
if (is24HourFormat()) {
int time = getInput();
fab.setEnabled(time % 100 <= 59);
} else {
// If on 12-hour clock, FAB will never be enabled
// until AM or PM is explicitly clicked.
fab.setEnabled(mAmPmState != UNSPECIFIED);
}
}
private void updateNumpadStates() {
updateAltButtonStates();
updateFabState();
updateBackspaceState();
updateNumberKeysStates();
}
private void updateNumberKeysStates() {
int cap = 10; // number of buttons
boolean is24hours = is24HourFormat();
if (count() == 0) {
enable(is24hours ? 0 : 1, cap);
return;
} else if (count() == MAX_DIGITS) {
enable(0, 0);
return;
}
int time = getInput();
if (is24hours) {
if (count() == 1) {
enable(0, time < 2 ? cap : 6);
} else if (count() == 2) {
enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 6);
} else if (count() == 3) {
if (time >= 236) {
enable(0, 0);
} else {
enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0);
}
}
} else {
if (count() == 1) {
if (time == 0) {
throw new IllegalStateException("12-hr format, zeroth digit = 0?");
} else {
enable(0, 6);
}
} else if (count() == 2 || count() == 3) {
if (time >= 126) {
enable(0, 0);
} else {
if (time >= 100 && time <= 125 && mAmPmState != UNSPECIFIED) {
// Could legally input fourth digit, if not for the am/pm state already set
enable(0, 0);
} else {
enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0);
}
}
}
}
}
// Helper method for inputting each character of an altBtn.
private void onAltButtonClick(Button altBtn) {
if (leftAlt != altBtn && rightAlt != altBtn)
throw new IllegalArgumentException("Not called with one of the alt buttons");
// Manually insert special characters for 12-hour clock
if (!is24HourFormat()) {
if (count() <= 2) {
// The colon is inserted for you
performClick(0);
performClick(0);
}
// text is AM or PM, so include space before
mFormattedInput.append(' ').append(altBtn.getText());
mAmPmState = leftAlt == altBtn ? AM : PM;
// Digits will be shown for you on click, but not AM/PM
notifyOnNumberInputListener(mFormattedInput.toString());
} else {
// Need to consider each character individually,
// as we are only interested in storing the digits
CharSequence text = altBtn.getText();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (Character.isDigit(c)) {
// Convert char to digit and perform click
// Colon is added for you
performClick(c);
}
}
mAmPmState = HRS_24;
}
updateNumpadStates();
}
private boolean is24HourFormat() {
return DateFormat.is24HourFormat(getContext());
}
private void inputNumber(CharSequence number) {
mFormattedInput.append(number);
// Add colon if necessary, depending on how many digits entered so far
if (count() == 3) {
// Insert a colon
int digits = getInput();
if (digits >= 60 && digits < 100 || digits >= 160 && digits < 200) {
// From 060-099 (really only to 095, but might as well go up to 100)
// From 160-199 (really only to 195, but might as well go up to 200),
// time does not exist if colon goes at pos. 1
mFormattedInput.insert(2, ':');
// These times only apply to the 24-hour clock, and if we're here,
// the time is not legal yet. So we can't set mAmPmState here for
// either clock.
// The 12-hour clock can only have mAmPmState set when AM/PM are clicked.
} else {
// A valid time exists if colon is at pos. 1
mFormattedInput.insert(1, ':');
// We can set mAmPmState here (and not in the above case) because
// the time here is legal in 24-hour clock
if (is24HourFormat()) {
mAmPmState = HRS_24;
}
}
} else if (count() == MAX_DIGITS) {
// Colon needs to move, so remove the colon previously added
mFormattedInput.deleteCharAt(mFormattedInput.indexOf(":"));
mFormattedInput.insert(2, ':');
// Time is legal in 24-hour clock
if (is24HourFormat()) {
mAmPmState = HRS_24;
}
}
// Moved to onClick()
//notifyOnNumberInputListener(mFormattedInput.toString());
}
public interface KeyListener extends Numpad.KeyListener {
void onAcceptChanges();
}
private void init() {
// Build alternative action buttons
leftAlt = (Button) buildButton(R.layout.numpad_alt_button, 3, 0);
rightAlt = (Button) buildButton(R.layout.numpad_alt_button, 3, 2);
leftAlt.setText(is24HourFormat() ? R.string.left_alt_24hr : R.string.am);
rightAlt.setText(is24HourFormat() ? R.string.right_alt_24hr : R.string.pm);
leftAlt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onAltButtonClick(leftAlt);
}
});
rightAlt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onAltButtonClick(rightAlt);
}
});
newRow();
buildBackspace(getChildCount() - 1, 2);
buildCollapse(getChildCount() - 1, 0);
// The FAB is wrapped in a FrameLayout
FrameLayout frame = (FrameLayout)
buildButton(R.layout.numpad_fab, getChildCount() - 1, 1);
fab = (FloatingActionButton) frame.getChildAt(0);
fab.setBackgroundTintList(getResources().getColorStateList(R.color.on_enabled_change_fab));
fab.setEnabled(false);
fab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (count() == 0) {
// Default time
setTime(0, 0);
}
KeyListener kl;
try {
kl = (KeyListener) getKeyListener();
} catch (ClassCastException e) {
throw new ClassCastException("Using AlarmNumpad with Numpad.KeyListener instead of AlarmNumpad.KeyListener");
} catch (NullPointerException e) {
throw new NullPointerException("Numpad's KeyListener is not set");
}
kl.onAcceptChanges();
}
});
updateNumpadStates();
}
}

View File

@ -1,12 +0,0 @@
package com.philliphsu.clock2.editalarm;
import com.philliphsu.clock2.Alarm;
/**
* Created by Phillip Hsu on 6/3/2016.
*/
@Deprecated
public interface AlarmUtilsHelper {
void scheduleAlarm(Alarm alarm);
void cancelAlarm(Alarm alarm, boolean showToast);
}

View File

@ -1,593 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.StringRes;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.SwitchCompat;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.BaseActivity;
import com.philliphsu.clock2.DaysOfWeek;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.SharedPreferencesHelper;
import com.philliphsu.clock2.alarms.AlarmActivity;
import com.philliphsu.clock2.model.AlarmLoader;
import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.AlarmUtils;
import com.philliphsu.clock2.util.DateFormatUtils;
import com.philliphsu.clock2.util.LocalBroadcastHelper;
import java.util.Date;
import butterknife.Bind;
import butterknife.OnClick;
import static android.text.format.DateFormat.getTimeFormat;
import static com.philliphsu.clock2.DaysOfWeek.SATURDAY;
import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
import static com.philliphsu.clock2.util.KeyboardUtils.hideKeyboard;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
/**
* TODO: Consider writing an EditAlarmController that would have an
* AlarmController member variable to manage the states of the alarm.
* The class would have the API for editing the alarm, so move all
* the relevant helper methods from here to there.
*/
@Deprecated
public class EditAlarmActivity extends BaseActivity implements
EditAlarmContract.View, // TODO: Remove @Override from the methods
AlarmUtilsHelper,
SharedPreferencesHelper,
LoaderManager.LoaderCallbacks<Alarm>,
BaseTimePickerDialog.OnTimeSetListener {
private static final String TAG = "EditAlarmActivity";
private static final String TAG_TIME_PICKER = "time_picker";
public static final String EXTRA_ALARM_ID = "com.philliphsu.clock2.editalarm.extra.ALARM_ID";
public static final String EXTRA_MODIFIED_ALARM = "com.philliphsu.clock2.editalarm.extra.MODIFIED_ALARM";
public static final String EXTRA_IS_DELETING = "com.philliphsu.clock2.editalarm.extra.IS_DELETING";
private static final String KEY_INPUT_TIME = "input_time";
private static final String KEY_ENABLED = "enabled";
private static final String KEY_CHECKED_DAYS = "checked_days";
private static final String KEY_LABEL = "label";
private static final String KEY_RINGTONE_URI = "ringtone";
private static final String KEY_VIBRATE = "vibrate";
private static final String KEY_SELECTED_HOUR = "selected_hour";
private static final String KEY_SELECTED_MINUTE = "selected_minute";
private static final int REQUEST_PICK_RINGTONE = 0;
private static final int ID_MENU_ITEM = 0;
private long mOldAlarmId;
private Uri mSelectedRingtoneUri;
private Alarm mOldAlarm;
private int mSelectedHourOfDay;
private int mSelectedMinute;
private final Intent mResultIntent = new Intent();
@Bind(R.id.main_content) CoordinatorLayout mMainContent;
@Bind(R.id.save) Button mSave;
@Bind(R.id.delete) Button mDelete;
@Bind(R.id.on_off) SwitchCompat mSwitch;
@Bind(R.id.input_time) TextView mTimeText;
@Bind({R.id.day0, R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6})
ToggleButton[] mDays;
@Bind(R.id.label) EditText mLabel;
@Bind(R.id.ringtone) Button mRingtone;
@Bind(R.id.vibrate) CheckBox mVibrate;
@Override
public void onTimeSet(ViewGroup viewGroup, int hourOfDay, int minute) {
mSelectedHourOfDay = hourOfDay;
mSelectedMinute = minute;
showTimeText(DateFormatUtils.formatTime(this, hourOfDay, minute));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setWeekDaysText();
// Are we recreating this Activity because of a rotation?
// If so, try finding the time picker in our backstack.
BaseTimePickerDialog picker = (BaseTimePickerDialog)
getSupportFragmentManager().findFragmentByTag(TAG_TIME_PICKER);
if (picker != null) {
// Restore the callback
picker.setOnTimeSetListener(this);
// mPicker = picker;
}
mOldAlarmId = getIntent().getLongExtra(EXTRA_ALARM_ID, -1);
if (mOldAlarmId != -1) {
// getLoaderManager() for support fragments by default returns the
// support version of LoaderManager. However, since this is an Activity,
// we have both the native getLoaderManager() and getSupportLoaderManager().
// Use the latter to remain consistent with the rest of our current code base.
getSupportLoaderManager().initLoader(0, null, this);
} else {
// Nothing to load, so show default values
showDetails();
// Show the time picker dialog, if it is not already showing
// AND this is the very first time the activity is being created
if (picker == null && savedInstanceState == null) {
// Wait a bit so the activity and dialog don't show at the same time
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
openTimePicker();
}
}, 300);
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Write out any state we wish to save for re-initialization.
// You can either restore this state in onCreate() or by
// overriding onRestoreInstanceState() and restoring it there.
super.onSaveInstanceState(outState);
outState.putString(KEY_INPUT_TIME, mTimeText.getText().toString());
// This is restored automatically post-rotation
outState.putBoolean(KEY_ENABLED, mSwitch.isChecked());
// These are restored automatically post-rotation
outState.putBooleanArray(KEY_CHECKED_DAYS, new boolean[] {
mDays[0].isChecked(),
mDays[1].isChecked(),
mDays[2].isChecked(),
mDays[3].isChecked(),
mDays[4].isChecked(),
mDays[5].isChecked(),
mDays[6].isChecked()
});
// This is restored automatically post-rotation
outState.putString(KEY_LABEL, mLabel.getText().toString());
outState.putParcelable(KEY_RINGTONE_URI, mSelectedRingtoneUri);
// This is restored automatically post-rotation
outState.putBoolean(KEY_VIBRATE, mVibrate.isChecked());
outState.putInt(KEY_SELECTED_HOUR, mSelectedHourOfDay);
outState.putInt(KEY_SELECTED_MINUTE, mSelectedMinute);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// For now, restore the previous state here instead of in onCreate()
// because it's easier than refactoring the code over there.
super.onRestoreInstanceState(savedInstanceState);
// No null-check on the given Bundle is necessary,
// because onSaveInstanceState() works with a non-null
// Bundle, even in the default implementation.
if (savedInstanceState.containsKey(KEY_INPUT_TIME)
//&& savedInstanceState.containsKey(KEY_LABEL)
&& savedInstanceState.containsKey(KEY_RINGTONE_URI)) {
// Make sure we actually saved something, or else we'd get
// a null and we'll end up clearing the hints...
mTimeText.setText(savedInstanceState.getString(KEY_INPUT_TIME));
// mLabel.setText(savedInstanceState.getString(KEY_LABEL));
mSelectedRingtoneUri = savedInstanceState.getParcelable(KEY_RINGTONE_URI);
// ...this, however, would throw an NPE because
// we'd be accessing a null Ringtone.
updateRingtoneButtonText();
}
mSelectedHourOfDay = savedInstanceState.getInt(KEY_SELECTED_HOUR);
mSelectedMinute = savedInstanceState.getInt(KEY_SELECTED_MINUTE);
// TODO: Manually restore the states of the "auto-restoring" widgets.
// In onCreate(), we will call showDetails().
// You only witnessed the auto-restoring for a blank Alarm, where
// the impl of showDetails() is pretty bare. If we have an actual
// Alarm, showDetails() could very well change the values displayed
// by those widgets based on that Alarm's values, but not based on
// any unsaved changes that may have occurred to the widgets previously.
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// TODO: Snooze menu item when alarm is ringing.
if (mOldAlarm != null && mOldAlarm.isEnabled()) {
int hoursBeforeUpcoming = getInt(R.string.key_notify_me_of_upcoming_alarms, 2);
// TODO: Schedule task with handler to show the menu item when it is time.
// Handler is fine because the task only needs to be done if the activity
// is being viewed. (I think) if the process of this
// app is killed, then the handler is also killed.
if ((mOldAlarm.ringsWithinHours(hoursBeforeUpcoming))) {
showCanDismissNow();
} else if (mOldAlarm.isSnoozed()) {
showSnoozed(new Date(mOldAlarm.snoozingUntil()));
}
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_dismiss_now:
case R.id.action_done_snoozing:
cancelAlarm(checkNotNull(mOldAlarm), true);
// cancelAlarm() should have turned off this alarm if appropriate
showEnabled(mOldAlarm.isEnabled());
item.setVisible(false);
// This only matters for case R.id.action_done_snoozing.
// It won't hurt to call this for case R.id.action_dismiss_now.
getSupportActionBar().setDisplayShowTitleEnabled(false);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Since this Activity doesn't host fragments, not necessary?
//super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PICK_RINGTONE && resultCode == RESULT_OK) {
mSelectedRingtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
updateRingtoneButtonText();
}
}
@Override
protected int layoutResId() {
return R.layout.activity_edit_alarm_v2;
}
@Override
protected int menuResId() {
return R.menu.menu_edit_alarm;
}
@OnClick(R.id.save)
void save() {
Alarm alarm = Alarm.builder()
.hour(mSelectedHourOfDay)
.minutes(mSelectedMinute)
.ringtone(getRingtone())
.label(getLabel())
.vibrates(vibrates())
.build();
alarm.setEnabled(isEnabled());
for (int i = SUNDAY; i <= SATURDAY; i++) {
alarm.setRecurring(i, isRecurringDay(i));
}
if (mOldAlarm != null) {
if (mOldAlarm.isEnabled()) {
Log.d(TAG, "Cancelling old alarm first");
cancelAlarm(mOldAlarm, false);
}
alarm.setId(mOldAlarm.id());
mResultIntent.putExtra(EXTRA_IS_DELETING, false);
}
mResultIntent.putExtra(EXTRA_MODIFIED_ALARM, alarm);
// The reason we don't schedule the alarm here is AlarmUtils
// will attempt to retrieve the specified alarm
// from the database; however, the alarm hasn't yet
// been added to the database at this point.
finish();
}
// TODO: Private accessor
@OnClick(R.id.delete)
void delete() {
if (mOldAlarm != null) {
if (mOldAlarm.isEnabled()) {
cancelAlarm(mOldAlarm, false);
// Re-enable in case this is restored so
// the alarm can be scheduled again. This
// change is saved to the db if the alarm
// is restored (re-inserting into to the db).
mOldAlarm.setEnabled(true);
}
mResultIntent.putExtra(EXTRA_IS_DELETING, true);
mResultIntent.putExtra(EXTRA_MODIFIED_ALARM, mOldAlarm);
}
finish();
}
@Override
public void finish() {
if (mResultIntent.getExtras() != null) {
setResult(RESULT_OK, mResultIntent);
}
super.finish();
}
@OnClick(R.id.input_time)
void openTimePicker() {
// Close the keyboard first, or else our dialog will be screwed up.
// If not open, this does nothing.
hideKeyboard(this); // This is only important for BottomSheetDialogs!
// Create a new instance each time we want to show the dialog.
// If we keep a reference to the dialog, we keep its previous state as well.
// So the next time we call show() on it, the input field will show the
// last inputted time.
BaseTimePickerDialog dialog = null;
String numpadStyle = getString(R.string.number_pad);
String gridStyle = getString(R.string.grid_selector);
String prefTimePickerStyle = PreferenceManager.getDefaultSharedPreferences(this).getString(
// key for the preference value to retrieve
getString(R.string.key_time_picker_style),
// default value
numpadStyle);
if (prefTimePickerStyle.equals(numpadStyle)) {
dialog = NumpadTimePickerDialog.newInstance(this);
} else if (prefTimePickerStyle.equals(gridStyle)) {
dialog = NumberGridTimePickerDialog.newInstance(
this, // OnTimeSetListener
0, // Initial hour of day
0, // Initial minute
DateFormat.is24HourFormat(this));
}
dialog.show(getSupportFragmentManager(), TAG_TIME_PICKER);
}
private void setWeekDaysText() {
for (int i = 0; i < mDays.length; i++) {
int weekDay = DaysOfWeek.getInstance(this).weekDayAt(i);
String label = DaysOfWeek.getLabel(weekDay);
mDays[i].setTextOn(label);
mDays[i].setTextOff(label);
mDays[i].setChecked(mDays[i].isChecked()); // force update the text, otherwise it won't be shown
}
}
private void updateRingtoneButtonText() {
mRingtone.setText(RingtoneManager.getRingtone(this, mSelectedRingtoneUri).getTitle(this));
}
@Override
public void showRecurringDays(int weekDay, boolean recurs) {
// What position in the week is this day located at?
int at = DaysOfWeek.getInstance(this).positionOf(weekDay);
// Toggle the button that corresponds to this day
mDays[at].setChecked(recurs);
}
@Override
public void showRingtone(String ringtone) {
// Initializing to Settings.System.DEFAULT_ALARM_ALERT_URI will show
// "Default ringtone (Name)" on the button text, and won't show the
// selection on the dialog when first opened. (unless you choose to show
// the default item in the intent extra?)
// Compare with getDefaultUri(int), which returns the symbolic URI instead of the
// actual sound URI. For TYPE_ALARM, this actually returns the same constant.
if (null == ringtone || ringtone.isEmpty()) {
mSelectedRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
} else {
mSelectedRingtoneUri = Uri.parse(ringtone);
}
updateRingtoneButtonText();
}
@Override
public void showVibrates(boolean vibrates) {
mVibrate.setChecked(vibrates);
}
@Deprecated
@Override
public void showEditorClosed() {
finish();
}
@Override
public int getHour() {
return mSelectedHourOfDay;
}
@Override
public int getMinutes() {
return mSelectedMinute;
}
@Override
public boolean isEnabled() {
return mSwitch.isChecked();
}
@Override
public boolean isRecurringDay(int weekDay) {
// What position in the week is this day located at?
int pos = DaysOfWeek.getInstance(this).positionOf(weekDay);
// Return the state of this day according to its button
return mDays[pos].isChecked();
}
@Override
public String getLabel() {
return mLabel.getText().toString();
}
@Override
public String getRingtone() {
return mSelectedRingtoneUri.toString();
}
@Override
public boolean vibrates() {
return mVibrate.isChecked();
}
@Override
public void showTime(int hour, int minutes) {
// TODO: Delete
showTimeText(DateFormatUtils.formatTime(this, hour, minutes));
}
@Override
public void showLabel(String label) {
mLabel.setText(label);
}
@Override
public void showEnabled(boolean enabled) {
mSwitch.setChecked(enabled);
}
@Override
public void showNumpad(boolean show) {
// TODO: Delete
}
@Override
public void showCanDismissNow() {
Menu menu = checkNotNull(getMenu());
MenuItem item = menu.findItem(R.id.action_dismiss_now);
if (!item.isVisible()) {
item.setVisible(true);
menu.findItem(R.id.action_done_snoozing).setVisible(false);
}
}
@Override
public void showSnoozed(Date snoozingUntilMillis) {
Menu menu = checkNotNull(getMenu());
MenuItem item = menu.findItem(R.id.action_done_snoozing);
if (!item.isVisible()) {
item.setVisible(true);
menu.findItem(R.id.action_dismiss_now).setVisible(false);
}
String title = getString(R.string.title_snoozing_until,
getTimeFormat(this).format(snoozingUntilMillis));
ActionBar ab = getSupportActionBar();
ab.setDisplayShowTitleEnabled(true);
ab.setTitle(title);
}
@Deprecated // TODO: Delete in favor of setDefaultTime()
@Override
public void setTimeTextHint() {
if (DateFormat.is24HourFormat(this)) {
mTimeText.setText(R.string.default_alarm_time_24h);
} else {
showTimeText(getString(R.string.default_alarm_time_12h));
}
}
private void setDefaultTime() {
mSelectedHourOfDay = 0;
mSelectedMinute = 0;
// TODO: We could simplify this to just showTimeText() with the only difference being
// the string that is passed in.
if (DateFormat.is24HourFormat(this)) {
mTimeText.setText(R.string.default_alarm_time_24h);
} else {
showTimeText(getString(R.string.default_alarm_time_12h));
}
}
@Override
public void showTimeText(String formattedInput) {
TimeTextUtils.setText(formattedInput, mTimeText);
}
@Deprecated
@Override
public void showTimeTextPostBackspace(String newStr) {
// TODO: Remove
}
@Override
@OnClick(R.id.ringtone)
public void showRingtonePickerDialog() {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
// The ringtone to show as selected when the dialog is opened
.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, mSelectedRingtoneUri)
// Whether to show "Default" item in the list
.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
// The ringtone that plays when default option is selected
//.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, DEFAULT_TONE);
startActivityForResult(intent, REQUEST_PICK_RINGTONE);
}
@Override
public void showTimeTextFocused(boolean focused) {
if (focused) {
mTimeText.requestFocus();
// Move cursor to end
} else {
mTimeText.clearFocus();
}
}
// TODO: Delete this
@Deprecated
@Override
public void scheduleAlarm(Alarm alarm) {
//AlarmUtils.scheduleAlarm(this, alarm, true);
}
@Override
public void cancelAlarm(Alarm alarm, boolean showToast) {
new AlarmController(this, mMainContent).cancelAlarm(alarm, true);
if (AlarmActivity.isAlive()) {
LocalBroadcastHelper.sendBroadcast(this, AlarmActivity.ACTION_FINISH);
}
}
@Override
public int getInt(@StringRes int key, int defaultValue) {
return AlarmUtils.readPreference(this, key, defaultValue);
}
@Override
public Loader<Alarm> onCreateLoader(int id, Bundle args) {
return new AlarmLoader(this, mOldAlarmId);
}
@Override
public void onLoadFinished(Loader<Alarm> loader, Alarm data) {
mOldAlarm = data;
showDetails();
}
@Override
public void onLoaderReset(Loader<Alarm> loader) {
// nothing to reset
}
// TODO: Privatize access of each method called here.
private void showDetails() {
if (mOldAlarm != null) {
showTime(mOldAlarm.hour(), mOldAlarm.minutes());
showEnabled(mOldAlarm.isEnabled());
for (int i = SUNDAY; i <= SATURDAY; i++) {
showRecurringDays(i, mOldAlarm.isRecurring(i));
}
showLabel(mOldAlarm.label());
showRingtone(mOldAlarm.ringtone());
showVibrates(mOldAlarm.vibrates());
showTimeTextFocused(false);
} else {
// TODO default values
setDefaultTime();
showTimeTextFocused(true);
showRingtone(""); // gets default ringtone
showEnabled(true);
}
}
}

View File

@ -1,53 +0,0 @@
package com.philliphsu.clock2.editalarm;
/**
* Created by Phillip Hsu on 6/2/2016.
*
* TODO: Consider NOT extending from AlarmContract. Instead, define the methods
* specific to this interface. Make a base implementation class of the base
* AlarmContract. When you create an implementation class of the more specific
* interface, all you need to do is implement the more specific interface.
* The base impl class will already implement the base interface methods.
* That way, each concrete interface impl doesn't need to implement the
* base interface methods again and again.
*/
@Deprecated
public interface EditAlarmContract {
interface View extends AlarmContract.View {
void showTime(int hour, int minutes);
void showRecurringDays(int weekDay, boolean recurs);
void showRingtone(String ringtone);
void showVibrates(boolean vibrates);
void showEditorClosed();
void showNumpad(boolean show);
void showRingtonePickerDialog();
void setTimeTextHint();
void showTimeText(String timeText);
void showTimeTextPostBackspace(String newStr);
void showTimeTextFocused(boolean focused);
int getHour();
int getMinutes();
boolean isEnabled();
boolean isRecurringDay(int weekDay);
String getLabel();
String getRingtone();
boolean vibrates();
}
interface Presenter extends AlarmContract.Presenter {
void loadAlarm(long id);
void save();
void delete();
void showNumpad();
void hideNumpad();
// not sure
void onBackspace(String newStr);
void acceptNumpadChanges();
void onPrepareOptionsMenu();
void openRingtonePickerDialog();
void setTimeTextHint();
void onNumberInput(String formattedInput);
void focusTimeText();
}
}

View File

@ -1,213 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.SharedPreferencesHelper;
import com.philliphsu.clock2.model.Repository;
import java.util.Date;
import static com.philliphsu.clock2.DaysOfWeek.SATURDAY;
import static com.philliphsu.clock2.DaysOfWeek.SUNDAY;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
/**
* Created by Phillip Hsu on 6/3/2016.
*/
@Deprecated
public class EditAlarmPresenter implements EditAlarmContract.Presenter {
private static final String TAG = "EditAlarmPresenter";
@NonNull private final EditAlarmContract.View mView;
@NonNull private final Repository<Alarm> mRepository;
@NonNull private final AlarmUtilsHelper mAlarmUtilsHelper;
@NonNull private final SharedPreferencesHelper mSharedPreferencesHelper;
@Nullable private Alarm mAlarm;
@Deprecated
public EditAlarmPresenter(@NonNull EditAlarmContract.View view,
@NonNull Repository<Alarm> repository,
@NonNull AlarmUtilsHelper helper,
@NonNull SharedPreferencesHelper sharedPreferencesHelper) {
mView = view;
mRepository = repository;
mAlarmUtilsHelper = helper;
mSharedPreferencesHelper = sharedPreferencesHelper;
}
@Deprecated
@Override
public void loadAlarm(long alarmId) {
// Can't load alarm in ctor because showDetails() calls
// showTime(), which calls setTime() on the numpad, which
// fires onNumberInput() events, which routes to the presenter,
// which would not be initialized yet because we still haven't
// returned from the ctor.
mAlarm = alarmId > -1 ? mRepository.getItem(alarmId) : null;
showDetails();
}
@Deprecated
@Override
public void save() {
int hour;
int minutes;
try {
hour = mView.getHour();
minutes = mView.getMinutes();
} catch (IllegalStateException e) {
Log.e(TAG, e.getMessage());
return;
}
Alarm a = Alarm.builder()
.hour(hour)
.minutes(minutes)
.ringtone(mView.getRingtone())
.label(mView.getLabel())
.vibrates(mView.vibrates())
.build();
a.setEnabled(mView.isEnabled());
for (int i = SUNDAY; i <= SATURDAY; i++) {
a.setRecurring(i, mView.isRecurringDay(i));
}
if (mAlarm != null) {
if (mAlarm.isEnabled()) {
Log.d(TAG, "Cancelling old alarm first");
mAlarmUtilsHelper.cancelAlarm(mAlarm, false);
}
mRepository.updateItem(mAlarm, a);
} else {
mRepository.addItem(a);
}
if (a.isEnabled()) {
mAlarmUtilsHelper.scheduleAlarm(a);
}
mView.showEditorClosed();
}
@Deprecated
@Override
public void delete() {
if (mAlarm != null) {
if (mAlarm.isEnabled()) {
mAlarmUtilsHelper.cancelAlarm(mAlarm, false);
}
mRepository.deleteItem(mAlarm);
}
mView.showEditorClosed();
}
@Deprecated
@Override
public void dismissNow() {
mAlarmUtilsHelper.cancelAlarm(checkNotNull(mAlarm), true);
// cancelAlarm() should have turned off this alarm if appropriate
mView.showEnabled(mAlarm.isEnabled());
}
@Deprecated
@Override
public void stopSnoozing() {
dismissNow(); // MUST be first, see AlarmUtils.notifyUpcomingAlarmIntent()
// AlarmUtils.cancelAlarm() does this for you if snoozed
/*
mAlarm.stopSnoozing(); // TOneverDO: before dismissNow()
mRepository.saveItems();
*/
}
@Deprecated
@Override
public void showNumpad() {
mView.showNumpad(true);
}
@Deprecated
@Override
public void hideNumpad() {
mView.showNumpad(false);
}
@Deprecated
@Override
public void onBackspace(String newStr) {
mView.showTimeTextPostBackspace(newStr);
}
@Deprecated
@Override
public void acceptNumpadChanges() {
mView.showNumpad(false);
mView.showEnabled(true);
}
@Deprecated
@Override
public void onPrepareOptionsMenu() {
if (mAlarm != null && mAlarm.isEnabled()) {
int hoursBeforeUpcoming = mSharedPreferencesHelper.getInt(R.string.key_notify_me_of_upcoming_alarms, 2);
// TODO: Schedule task with handler to show the menu item when it is time. Handler is fine because
// the task only needs to be done if the activity is being viewed. (I think) if the process of this
// app is killed, then the handler is also killed.
if ((mAlarm.ringsWithinHours(hoursBeforeUpcoming))) {
mView.showCanDismissNow();
} else if (mAlarm.isSnoozed()) {
mView.showSnoozed(new Date(mAlarm.snoozingUntil()));
}
}
}
@Deprecated
@Override
public void openRingtonePickerDialog() {
mView.showRingtonePickerDialog();
}
@Deprecated
@Override
public void setTimeTextHint() {
mView.setTimeTextHint();
}
@Deprecated
@Override
public void onNumberInput(String formattedInput) {
mView.showTimeText(formattedInput);
}
@Deprecated
@Override
public void focusTimeText() {
mView.showTimeTextFocused(true);
}
@Deprecated
private void showDetails() {
if (mAlarm != null) {
mView.showTime(mAlarm.hour(), mAlarm.minutes());
mView.showEnabled(mAlarm.isEnabled());
for (int i = SUNDAY; i <= SATURDAY; i++) {
mView.showRecurringDays(i, mAlarm.isRecurring(i));
}
mView.showLabel(mAlarm.label());
mView.showRingtone(mAlarm.ringtone());
mView.showVibrates(mAlarm.vibrates());
// Editing so don't show
mView.showNumpad(false);
mView.showTimeTextFocused(false);
} else {
// TODO default values
mView.showTimeTextFocused(true);
mView.showRingtone(""); // gets default ringtone
mView.showNumpad(true);
}
}
}

View File

@ -23,9 +23,6 @@ import butterknife.OnClick;
* Created by Phillip Hsu on 7/12/2016.
*
* Successor to the Numpad class that was based on TableLayout.
* Unlike Numpad, this class only manages the logic for number button clicks
* and not the backspace button. However, we do provide an API for removing
* digits from the input.
*
* TODO: Is NumpadTimePicker the only subclass? If so, why do we need this
* superclass? If we move the contents of this class to NumpadTimePicker,

View File

@ -1,155 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.content.Context;
import android.support.v7.widget.GridLayout;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import com.philliphsu.clock2.R;
/**
* Created by Phillip Hsu on 7/21/2016.
*/
@Deprecated
public class NumbersGridView extends GridLayout {
private static final String TAG = "NumberGrid";
private static final int COLUMNS = 3;
// private CircularIndicatorSetter mIndicatorSetter;
private OnNumberSelectedListener mSelectionListener;
// TODO: Since we plan to dynamically clear and inflate children into this
// parent to represent "pages", this seems useless? Since each page has at
// most one selection, and at any given time, this GridLayout can only show
// one page at a time. Instead, when the onNumberSelected() is fired, the
// hosting dialog should keep a reference to the returned number. It is up
// to the dialog to deduce what time field the number represents, probably
// with an int flag that indicates what "page" this GridLayout is displaying.
private int mSelection;
public interface OnNumberSelectedListener {
void onNumberSelected(int number);
}
public NumbersGridView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public NumbersGridView(Context context) {
super(context);
init();
}
public void setOnNumberSelectedListener(OnNumberSelectedListener onNumberSelectedListener) {
mSelectionListener = onNumberSelectedListener;
}
// /*package*/ void setTheme(Context context, boolean themeDark) {
// for (int i = 0; i < getChildCount(); i++) {
// View view = getChildAt(i);
// if (view instanceof TextViewWithCircularIndicator) {
// TextViewWithCircularIndicator text = (TextViewWithCircularIndicator) getChildAt(i);
// text.setTheme(context, themeDark);
// }
// }
// }
public int getSelection() {
return mSelection;
}
public void setSelection(int value) {
mSelection = value;
// for (int i = 0; i < getChildCount(); i++) {
// View v = getChildAt(i);
// if (v instanceof TextViewWithCircularIndicator) {
// TextViewWithCircularIndicator text = (TextViewWithCircularIndicator) v;
// // parseInt() strips out leading zeroes
// int num = Integer.parseInt(text.getText().toString());
// if (value == num) {
// mIndicatorSetter.setIndicator(text);
// break;
// }
// } else {
// // We have reached a non-numeric button, i.e. the minute tuners, unless you have
// // other non-numeric buttons as well. This means we iterated through all numeric
// // buttons, but this value is not one of the preset values.
// // Clear the indicator from the default selection.
// mIndicatorSetter.setIndicator(null);
// break;
// }
// }
}
/**
* Set the numbers to be displayed in this grid.
*/
public void setNumbers(int[] numbers) {
// TODO: This method isn't applicable to the 24 Hour grid.. consider creating a subclass
// just for 24 hour values? Or just use a regular GridLayout in the dialog's layout
// as the container for whatever arbitrary children you want?
// TODO: Depending on the user's clock system, there will be different logic to toggle
// between "pages". If the user uses 12-hour time, then the same NumberGrid can be reused
// for both pages, and you can use this method to replace the texts. Otherwise, you have to
// remove all of the 24-hour value items from this grid and inflate the minutes layout
// into this grid. Find an elegant solution to implement this logic.
setNumbers(numbers, false);
}
public void setNumbers(int[] numbers, boolean zeroPadSingleDigits) {
if (numbers != null) {
int i = 0;
View child;
while ((child = getChildAt(i)) instanceof TextView/*TODO: TextViewWithCircularIndicator*/) {
String s = zeroPadSingleDigits
? String.format("%02d", numbers[i])
: String.valueOf(numbers[i]);
((TextView) child).setText(s);
child.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
setNumbers(new int[] {0,5,10,15,20,25,30,35,40,45,50,55}, true);
inflate(getContext(), R.layout.content_minutes_grid, NumbersGridView.this);
}
});
i++;
}
}
}
/**
* Final because this is implemented for the grid of numbers. If subclasses need their own
* click listeners for non-numeric buttons, they should set new OnClickListeners on those buttons.
*/
// @Override
// public final void onClick(View v) {
// TextViewWithCircularIndicator view = (TextViewWithCircularIndicator) v;
// String text = view.getText().toString();
// int number = Integer.parseInt(text);
// mSelection = number;
// fireOnNumberSelectedEvent(number);
// mIndicatorSetter.setIndicator(view);
// }
protected void fireOnNumberSelectedEvent(int number) {
if (mSelectionListener != null)
mSelectionListener.onNumberSelected(number);
}
private void init() {
// setAlignmentMode(ALIGN_BOUNDS);
setColumnCount(COLUMNS);
// When we initialize, display the hour values "page".
boolean is24HourMode = DateFormat.is24HourFormat(getContext());
int layout = is24HourMode
? R.layout.content_24h_number_grid
: R.layout.content_hours_grid;
inflate(getContext(), layout, this);
if (!is24HourMode) {
setNumbers(new int[] {1,2,3,4,5,6,7,8,9,10,11,12});
}
// ButterKnife.bind(this);
}
}

View File

@ -1,138 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.view.View;
/**
* Created by Phillip Hsu on 7/20/2016.
*
* TODO: This class has NOT been properly written yet. Consider moving all button state code
* from the Numpad classes to here.
* TODO: We might need to write a setAmPmState() method in NumpadTimePicker.
*
* NumpadTimePickerDialog would store a reference to this controller and use this to
* update the states of the various buttons in its layout. Currently, this class has
* ported over updateNumpadStates() and its related methods from NumpadTimePicker.
*/
public class NumpadTimePickerController {
private NumpadTimePicker mPicker;
private View mConfirmSelectionButton;
private View mLeftAltButton;
private View mRightAltButton;
private boolean mIs24HourMode;
/*
public NumpadTimePickerController(NumpadTimePicker picker, View confirmSelectionButton,
View leftAltButton, View rightAltButton, boolean is24HourMode) {
mPicker = picker;
mConfirmSelectionButton = confirmSelectionButton;
mLeftAltButton = leftAltButton;
mRightAltButton = rightAltButton;
mIs24HourMode = is24HourMode;
}
public void updateNumpadStates() {
// TOneverDO: after updateNumberKeysStates(), esp. if clock is 12-hour,
// because it calls mPicker.enable(0, 0), which checks if the alt buttons have been
// disabled as well before firing the onInputDisabled().
updateAltButtonStates();
updateBackspaceState();
updateNumberKeysStates();
updateFabState();
}
public void updateFabState() {
mConfirmSelectionButton.setEnabled(mPicker.checkTimeValid());
}
public void updateBackspaceState() {
mPicker.setBackspaceEnabled(mPicker.count() > 0);
}
public void updateAltButtonStates() {
if (mPicker.count() == 0) {
// No input, no access!
mLeftAltButton.setEnabled(false);
mRightAltButton.setEnabled(false);
} else if (mPicker.count() == 1) {
// Any of 0-9 inputted, always have access in either clock.
mLeftAltButton.setEnabled(true);
mRightAltButton.setEnabled(true);
} else if (mPicker.count() == 2) {
// Any 2 digits that make a valid hour for either clock are eligible for access
int time = mPicker.getInput();
boolean validTwoDigitHour = mIs24HourMode ? time <= 23 : time >= 10 && time <= 12;
mLeftAltButton.setEnabled(validTwoDigitHour);
mRightAltButton.setEnabled(validTwoDigitHour);
} else if (mPicker.count() == 3) {
if (mIs24HourMode) {
// For the 24-hour clock, no access at all because
// two more digits (00 or 30) cannot be added to 3 digits.
mLeftAltButton.setEnabled(false);
mRightAltButton.setEnabled(false);
} else {
// True for any 3 digits, if AM/PM not already entered
boolean enabled = mAmPmState == UNSPECIFIED;
mLeftAltButton.setEnabled(enabled);
mRightAltButton.setEnabled(enabled);
}
} else if (mPicker.count() == mPicker.capacity()) {
// 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 = !mIs24HourMode && mAmPmState == UNSPECIFIED;
mLeftAltButton.setEnabled(enabled);
mRightAltButton.setEnabled(enabled);
}
}
public void updateNumberKeysStates() {
int cap = 10; // number of buttons
boolean is24hours = mIs24HourMode;
if (mPicker.count() == 0) {
mPicker.enable(is24hours ? 0 : 1, cap);
return;
} else if (mPicker.count() == mPicker.capacity()) {
mPicker.enable(0, 0);
return;
}
int time = mPicker.getInput();
if (is24hours) {
if (mPicker.count() == 1) {
mPicker.enable(0, time < 2 ? cap : 6);
} else if (mPicker.count() == 2) {
mPicker.enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 6);
} else if (mPicker.count() == 3) {
if (time >= 236) {
mPicker.enable(0, 0);
} else {
mPicker.enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0);
}
}
} else {
if (mPicker.count() == 1) {
if (time == 0) {
throw new IllegalStateException("12-hr format, zeroth digit = 0?");
} else {
mPicker.enable(0, 6);
}
} else if (mPicker.count() == 2 || mPicker.count() == 3) {
if (time >= 126) {
mPicker.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
mPicker.enable(0, 0);
} else {
mPicker.enable(0, time % 10 >= 0 && time % 10 <= 5 ? cap : 0);
}
}
}
}
}
*/
}

View File

@ -9,6 +9,7 @@ import android.widget.TextView;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.aospdatetimepicker.Utils;
import com.philliphsu.clock2.util.TimeTextUtils;
import butterknife.Bind;
import butterknife.OnClick;

View File

@ -1,28 +0,0 @@
package com.philliphsu.clock2.editalarm;
import android.view.ViewGroup;
/**
* Created by Phillip Hsu on 7/12/2016.
*
* The callback interface used to indicate the user is done filling in
* the time (they clicked on the 'Set' button).
*/
public interface TimePicker {
int hourOfDay();
int minute();
/**
* The callback interface used to indicate the user is done filling in
* the time (they clicked on the 'Set' button).
*/
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 changing VG param to TimePicker
void onTimeSet(ViewGroup viewGroup, int hourOfDay, int minute);
}
}

View File

@ -1,25 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import android.support.annotation.NonNull;
import com.philliphsu.clock2.Alarm;
import org.json.JSONObject;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
@Deprecated
public class AlarmIoHelper extends JsonIoHelper<Alarm> {
private static final String FILENAME = "alarms.json";
public AlarmIoHelper(@NonNull Context context) {
super(context, FILENAME);
}
@Override
protected Alarm newItem(@NonNull JSONObject jsonObject) {
return Alarm.create(jsonObject);
}
}

View File

@ -1,25 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import com.philliphsu.clock2.Alarm;
/**
* Created by Phillip Hsu on 6/30/2016.
*/
public class AlarmLoader extends DataLoader<Alarm> {
private long mAlarmId;
// TODO: Consider writing a super ctor that has the id param, so
// subclasses don't need to write their own.
public AlarmLoader(Context context, long alarmId) {
super(context);
mAlarmId = alarmId;
}
@Override
public Alarm loadInBackground() {
return new AlarmsTableManager(getContext()).queryItem(mAlarmId).getItem();
}
}

View File

@ -1,37 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.philliphsu.clock2.Alarm;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
@Deprecated
public class AlarmsRepository extends BaseRepository<Alarm> {
private static final String TAG = "AlarmsRepository";
// Singleton, so this is the sole instance for the lifetime
// of the application; thus, instance fields do not need to
// be declared static because they are already associated with
// this single instance. Since no other instance can exist,
// any member fields are effectively class fields.
// **
// Can't be final, otherwise you'd need to instantiate inline
// or in static initializer, but ctor requires Context so you
// can't do that either.
private static AlarmsRepository sRepo;
private AlarmsRepository(@NonNull Context context) {
super(context, new AlarmIoHelper(context));
}
public static AlarmsRepository getInstance(@NonNull Context context) {
if (null == sRepo) {
Log.d(TAG, "Loading AlarmsRepository for the first time");
sRepo = new AlarmsRepository(context);
}
return sRepo;
}
}

View File

@ -1,135 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
@Deprecated
public abstract class BaseRepository<T extends JsonSerializable> implements Repository<T> {
private static final String TAG = "BaseRepository";
// Cannot do this! Multiple classes will extend from this,
// so this "singleton" would be a class property for all of them.
//private static BaseRepositoryV2<T> sInstance;
// Never used since subclasses provide the ioHelper, but I think
// the intention is that we hold onto the global context so this
// never gets GCed for the lifetime of the app.
@NonNull private final Context mContext;
@NonNull private final List<T> mItems; // TODO: Consider ArrayMap<Long, T>?
@NonNull private final JsonIoHelper<T> mIoHelper;
// TODO: Test that the callbacks work.
private DataObserver<T> mDataObserver;
// We could use T but since it's already defined, we should avoid
// the needless confusion and use a different type param. You won't
// be able to refer to the type that T resolves to anyway.
public interface DataObserver<T2> {
void onItemAdded(T2 item);
void onItemDeleted(T2 item);
void onItemUpdated(T2 oldItem, T2 newItem);
}
/*package-private*/ BaseRepository(@NonNull Context context,
@NonNull JsonIoHelper<T> ioHelper) {
Log.d(TAG, "BaseRepositoryV2 initialized");
mContext = context.getApplicationContext();
mIoHelper = ioHelper; // MUST precede loading items
mItems = loadItems(); // TOneverDO: move this elsewhere
}
@Override @NonNull
public List<T> getItems() {
return Collections.unmodifiableList(mItems);
}
@Nullable
@Override
public T getItem(long id) {
for (T item : getItems())
if (item.id() == id)
return item;
return null;
}
@Override
public final void addItem(@NonNull T item) {
Log.d(TAG, "New item added");
mItems.add(item);
mDataObserver.onItemAdded(item); // TODO: Set StopwatchView as DataObserver
saveItems();
}
@Override
public final void deleteItem(@NonNull T item) {
if (!mItems.remove(item)) {
Log.e(TAG, "Cannot remove an item that is not in the list");
} else {
mDataObserver.onItemDeleted(item); // TODO: Set StopwatchView as DataObserver
saveItems();
}
}
@Override
public final void updateItem(@NonNull T item1, @NonNull T item2) {
// TODO: Won't work unless objects are immutable, so item1
// can't change and thus its index will never change
// **
// Actually, since the items come from this list,
// modifications to items will directly "propagate".
// In the process, the index of that modified item
// has not changed. If that's the case, there really
// isn't any point for an update method, especially
// since item2 would be unnecessary and won't even need
// to be used.
mItems.set(mItems.indexOf(item1), item2);
mDataObserver.onItemUpdated(item1, item2); // TODO: Set StopwatchView as DataObserver
saveItems();
}
@Override
public final boolean saveItems() {
try {
mIoHelper.saveItems(mItems);
Log.d(TAG, "Saved items to file");
return true;
} catch (IOException e) {
Log.e(TAG, "Error writing items to file: " + e);
return false;
}
}
@Override
public final void clear() {
mItems.clear();
saveItems();
}
public final void registerDataObserver(@NonNull DataObserver<T> observer) {
mDataObserver = observer;
}
// TODO: Do we need to call this?
public final void unregisterDataObserver() {
mDataObserver = null;
}
@NonNull
private List<T> loadItems() {
try {
return mIoHelper.loadItems();
} catch (IOException e) {
Log.e(TAG, "Error loading items from file: " + e);
return new ArrayList<>();
}
}
}

View File

@ -1,37 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
/**
* Created by Phillip Hsu on 6/30/2016.
*/
// TODO: Consider adding a DatabaseTableManager type param, so we can then
// implement loadInBackground for subclasses. You would, however, need to write
// an abstract method getTableManager() that subclasses implement for us.
public abstract class DataLoader<D> extends AsyncTaskLoader<D> {
private D mData;
public DataLoader(Context context) {
super(context);
}
@Override
protected void onStartLoading() {
if (mData != null) {
deliverResult(mData);
} else {
forceLoad();
}
}
@Override
public void deliverResult(D data) {
mData = data;
if (isStarted()) {
super.deliverResult(data);
}
}
}

View File

@ -1,94 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
@Deprecated
public abstract class JsonIoHelper<T extends JsonSerializable> {
@NonNull private final Context mContext;
@NonNull private final String mFilename;
public JsonIoHelper(@NonNull Context context,
@NonNull String filename) {
mContext = context.getApplicationContext();
mFilename = filename;
}
protected abstract T newItem(@NonNull JSONObject jsonObject);
public final List<T> loadItems() throws IOException {
ArrayList<T> items = new ArrayList<>();
BufferedReader reader = null;
try {
// Opens the file in a FileInputStream for byte-reading
InputStream in = mContext.openFileInput(mFilename);
// Use an InputStreamReader to convert bytes to characters. A BufferedReader wraps the
// existing Reader and provides a buffer (a cache) for storing the characters.
// From https://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html:
// "In general, each read request made of a Reader causes a corresponding read request
// to be made of the underlying character or byte stream. It is therefore advisable to
// wrap a BufferedReader around any Reader whose read() operations may be costly, such as
// FileReaders and InputStreamReaders."
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder jsonString = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
// Line breaks are omitted and irrelevant
jsonString.append(line);
}
// JSONTokener parses a String in JSON "notation" into a "JSON-compatible" object.
// JSON objects are instances of JSONObject and JSONArray. You actually have to call
// nextValue() on the returned Tokener to get the corresponding JSON object.
JSONArray array = (JSONArray) new JSONTokener(jsonString.toString()).nextValue();
for (int i = 0; i < array.length(); i++) {
items.add(newItem(array.getJSONObject(i)));
}
} catch (JSONException e) {
throw new RuntimeException(e);
} finally {
if (reader != null)
reader.close();
}
return items;
}
public final void saveItems(@NonNull List<T> items) throws IOException {
// Convert items to JSONObjects and store in a JSONArray
JSONArray array = new JSONArray();
try {
for (T item : items) {
array.put(item.toJsonObject());
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
OutputStreamWriter writer = null;
try {
// Create a character stream from the byte stream
writer = new OutputStreamWriter(mContext.openFileOutput(mFilename, Context.MODE_PRIVATE));
// Write JSONArray to file
writer.write(array.toString());
} finally {
if (writer != null) {
writer.close(); // also calls close on the byte stream
}
}
}
}

View File

@ -1,17 +0,0 @@
package com.philliphsu.clock2.model;
import android.support.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
@Deprecated
public interface JsonSerializable {
String KEY_ID = "id";
@NonNull JSONObject toJsonObject() throws JSONException;
long id();
}

View File

@ -1,19 +0,0 @@
package com.philliphsu.clock2.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.List;
/**
* Created by Phillip Hsu on 5/31/2016.
*/
public interface Repository<T> {
@NonNull List<T> getItems();
@Nullable T getItem(long id);
void addItem(@NonNull T item);
void deleteItem(@NonNull T item);
void updateItem(@NonNull T item1, @NonNull T item2);
boolean saveItems();
void clear();
}

View File

@ -1,25 +0,0 @@
package com.philliphsu.clock2.model;
import android.content.Context;
import com.philliphsu.clock2.Timer;
/**
* Created by Phillip Hsu on 8/3/2016.
*/
public class TimerLoader extends DataLoader<Timer> {
private long mTimerId;
// TODO: Consider writing a super ctor that has the id param, so
// subclasses don't need to write their own.
public TimerLoader(Context context, long timerId) {
super(context);
mTimerId = timerId;
}
@Override
public Timer loadInBackground() {
return new TimersTableManager(getContext()).queryItem(mTimerId).getItem();
}
}

View File

@ -1,40 +0,0 @@
package com.philliphsu.clock2.timers;
import android.view.ViewGroup;
import com.philliphsu.clock2.BaseAdapter;
import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.Timer;
import java.util.List;
/**
* Created by Phillip Hsu on 7/26/2016.
*/
@Deprecated
public class TimerAdapter extends BaseAdapter<Timer, TimerViewHolder> {
public TimerAdapter(List<Timer> items, OnListItemInteractionListener<Timer> listener) {
super(Timer.class, items, listener);
}
@Override
protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener) {
return new TimerViewHolder(parent, listener, null);
}
@Override
protected int compare(Timer o1, Timer o2) {
return 0;
}
@Override
protected boolean areContentsTheSame(Timer oldItem, Timer newItem) {
return false;
}
@Override
protected boolean areItemsTheSame(Timer item1, Timer item2) {
return false;
}
}

View File

@ -1,37 +0,0 @@
package com.philliphsu.clock2.timers.dummy;
import com.philliphsu.clock2.Timer;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
* <p/>
* TODO: Replace all uses of this class before publishing your app.
*/
public class DummyContent {
/**
* An array of sample (dummy) items.
*/
public static final List<Timer> ITEMS = new ArrayList<>();
private static final int COUNT = 10;
static {
// Add some sample items.
for (int i = 1; i <= COUNT; i++) {
addItem(createTimer(i));
}
}
private static void addItem(Timer item) {
ITEMS.add(item);
}
private static Timer createTimer(int position) {
return Timer.create(1, 0, 0);
}
}

View File

@ -19,7 +19,7 @@ import com.philliphsu.clock2.model.AlarmsTableManager;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.getActivity;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.TimeFormatUtils.formatTime;
import static java.util.concurrent.TimeUnit.HOURS;
/**
@ -135,8 +135,8 @@ public final class AlarmController {
// passes before rescheduling the alarm.
alarm.ignoreUpcomingRingTime(true); // Useful only for VH binding
Intent intent = new Intent(mAppContext, PendingAlarmScheduler.class)
.putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, alarm.id());
pi = PendingIntent.getBroadcast(mAppContext, alarm.intId(),
.putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, alarm.getId());
pi = PendingIntent.getBroadcast(mAppContext, alarm.getIntId(),
intent, PendingIntent.FLAG_ONE_SHOT);
am.set(AlarmManager.RTC_WAKEUP, alarm.ringsAt(), pi);
} else {
@ -177,7 +177,7 @@ public final class AlarmController {
new Thread(new Runnable() {
@Override
public void run() {
mTableManager.updateItem(alarm.id(), alarm);
mTableManager.updateItem(alarm.getId(), alarm);
}
}).start();
}
@ -187,7 +187,7 @@ public final class AlarmController {
Intent intent = new Intent(mAppContext, AlarmActivity.class)
.putExtra(AlarmActivity.EXTRA_RINGING_OBJECT, alarm);
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(mAppContext, alarm.intId(), intent, flag);
PendingIntent pi = getActivity(mAppContext, alarm.getIntId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
// null can be returned for some reason.
/*
@ -208,7 +208,7 @@ public final class AlarmController {
intent.setAction(UpcomingAlarmReceiver.ACTION_SHOW_SNOOZING);
}
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = PendingIntent.getBroadcast(mAppContext, alarm.intId(), intent, flag);
PendingIntent pi = PendingIntent.getBroadcast(mAppContext, alarm.getIntId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
// null can be returned for some reason.
/*

View File

@ -1,161 +1,21 @@
package com.philliphsu.clock2.util;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
import android.support.annotation.StringRes;
import android.util.Log;
import android.widget.Toast;
import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.PendingAlarmScheduler;
import com.philliphsu.clock2.R;
import com.philliphsu.clock2.UpcomingAlarmReceiver;
import com.philliphsu.clock2.alarms.AlarmActivity;
import com.philliphsu.clock2.alarms.AlarmRingtoneService;
import com.philliphsu.clock2.model.AlarmsTableManager;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.getActivity;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static java.util.concurrent.TimeUnit.HOURS;
/**
* Created by Phillip Hsu on 6/3/2016.
*
* Utilities for scheduling and unscheduling alarms with the {@link AlarmManager}, as well as
* managing the upcoming alarm notification.
*
* TODO: Adapt this to Timers too...
* TODO: Keep only utility methods. Not the scheduling and cancelling methods.
* Utilities for reading alarm preferences.
*/
public final class AlarmUtils {
private static final String TAG = "AlarmUtils";
private AlarmUtils() {}
/**
* Schedules the alarm with the {@link AlarmManager}. If
* {@code alarm.}{@link Alarm#isEnabled() isEnabled()} returns false,
* this does nothing and returns immediately.
*
* @deprecated {@code showToast} is no longer working. Callers must
* handle popup confirmations on their own.
*/
// TODO: Consider moving usages to the background
public static void scheduleAlarm(Context context, Alarm alarm, boolean showToast) {
if (!alarm.isEnabled()) {
Log.i(TAG, "Skipped scheduling an alarm because it was not enabled");
return;
}
Log.d(TAG, "Scheduling alarm " + alarm);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// If there is already an alarm for this Intent scheduled (with the equality of two
// intents being defined by filterEquals(Intent)), then it will be removed and replaced
// by this one. For most of our uses, the relevant criteria for equality will be the
// action, the data, and the class (component). Although not documented, the request code
// of a PendingIntent is also considered to determine equality of two intents.
// WAKEUP alarm types wake the CPU up, but NOT the screen. If that is what you want, you need
// to handle that yourself by using a wakelock, etc..
// We use a WAKEUP alarm to send the upcoming alarm notification so it goes off even if the
// device is asleep. Otherwise, it will not go off until the device is turned back on.
long ringAt = alarm.isSnoozed() ? alarm.snoozingUntil() : alarm.ringsAt();
// If snoozed, upcoming note posted immediately.
am.set(AlarmManager.RTC_WAKEUP, ringAt - HOURS.toMillis(hoursBeforeUpcoming(context)),
notifyUpcomingAlarmIntent(context, alarm, false));
am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent(context, alarm, false));
// TODO: Consider removing this and letting callers handle this, because
// it could be beneficial for callers to schedule the alarm in a worker thread.
if (false && showToast) {
String message;
if (alarm.isSnoozed()) {
message = context.getString(R.string.title_snoozing_until,
formatTime(context, alarm.snoozingUntil()));
} else {
message = context.getString(R.string.alarm_set_for,
DurationUtils.toString(context, alarm.ringsIn(), false /*abbreviate?*/));
}
// TODO: Will toasts show for any Context? e.g. IntentService can't do anything on UI thread.
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
public static void cancelAlarm(Context c, Alarm a, boolean showToast) {
Log.d(TAG, "Cancelling alarm " + a);
AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = alarmIntent(c, a, true);
if (pi != null) {
am.cancel(pi);
pi.cancel();
}
pi = notifyUpcomingAlarmIntent(c, a, true);
if (pi != null) {
am.cancel(pi);
pi.cancel();
}
removeUpcomingAlarmNotification(c, a);
// We can't remove this and instead let callers handle Toasts
// without complication, because callers would then have to
// handle cases when the alarm is snoozed and/or has
// recurrence themselves.
// TOneverDO: Place block after making value changes to the alarm.
if (showToast && (a.ringsWithinHours(hoursBeforeUpcoming(c)) || a.isSnoozed())) {
String time = formatTime(c, a.isSnoozed() ? a.snoozingUntil() : a.ringsAt());
String text = c.getString(R.string.upcoming_alarm_dismissed, time);
Toast.makeText(c, text, Toast.LENGTH_LONG).show();
}
if (a.isSnoozed()) {
a.stopSnoozing();
}
if (!a.hasRecurrence()) {
a.setEnabled(false);
} else {
if (a.isEnabled()) {
if (a.ringsWithinHours(hoursBeforeUpcoming(c))) {
// Still upcoming today, so wait until the normal ring time
// passes before rescheduling the alarm.
a.ignoreUpcomingRingTime(true); // Useful only for VH binding
Intent intent = new Intent(c, PendingAlarmScheduler.class)
.putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, a.id());
pi = PendingIntent.getBroadcast(c, a.intId(), intent, PendingIntent.FLAG_ONE_SHOT);
am.set(AlarmManager.RTC_WAKEUP, a.ringsAt(), pi);
} else {
scheduleAlarm(c, a, false);
}
}
}
save(c, a);
// If service is not running, nothing happens
c.stopService(new Intent(c, AlarmRingtoneService.class));
}
public static void snoozeAlarm(Context c, Alarm a) {
a.snooze(snoozeDuration(c));
scheduleAlarm(c, a, true);
save(c, a);
}
public static void removeUpcomingAlarmNotification(Context c, Alarm a) {
Intent intent = new Intent(c, UpcomingAlarmReceiver.class)
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION)
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM, a);
c.sendBroadcast(intent);
}
public static int snoozeDuration(Context c) {
return readPreference(c, R.string.key_snooze_duration, 10);
}
@ -177,51 +37,4 @@ public final class AlarmUtils {
String value = PreferenceManager.getDefaultSharedPreferences(c).getString(c.getString(key), null);
return null == value ? defaultValue : Integer.parseInt(value);
}
private static PendingIntent alarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
// TODO: Use appropriate subclass instead
Intent intent = new Intent(context, AlarmActivity.class)
.putExtra(AlarmActivity.EXTRA_RINGING_OBJECT, alarm);
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = getActivity(context, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
// null can be returned for some reason.
/*
if (retrievePrevious) {
checkNotNull(pi);
}
*/
return pi;
}
private static PendingIntent notifyUpcomingAlarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
Intent intent = new Intent(context, UpcomingAlarmReceiver.class)
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM, alarm);
if (alarm.isSnoozed()) {
// TODO: Will this affect retrieving a previous instance? Say if the previous instance
// didn't have this action set initially, but at a later time we made a new instance
// with it set.
intent.setAction(UpcomingAlarmReceiver.ACTION_SHOW_SNOOZING);
}
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
PendingIntent pi = PendingIntent.getBroadcast(context, alarm.intId(), intent, flag);
// Even when we try to retrieve a previous instance that actually did exist,
// null can be returned for some reason.
/*
if (retrievePrevious) {
checkNotNull(pi);
}
*/
return pi;
}
public static void save(final Context c, final Alarm alarm) {
// TODO: Will using the Runnable like this cause a memory leak?
new Thread(new Runnable() {
@Override
public void run() {
new AlarmsTableManager(c).updateItem(alarm.id(), alarm);
}
}).start();
}
}

View File

@ -1,27 +0,0 @@
package com.philliphsu.clock2.util;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.TypedValue;
/**
* Created by Phillip Hsu on 4/21/2016.
*/
public final class ConversionUtils {
public static float dpToPx(@NonNull final Context context, final float dp) {
return toPx(context, TypedValue.COMPLEX_UNIT_DIP, dp);
}
public static float spToPx(@NonNull final Context context, final float sp) {
return toPx(context, TypedValue.COMPLEX_UNIT_SP, sp);
}
private static float toPx(@NonNull final Context context, final int unit, final float val) {
// This always returns a floating point value, i.e. pixels.
return TypedValue.applyDimension(unit, val, context.getResources().getDisplayMetrics());
}
private ConversionUtils() {}
}

View File

@ -10,9 +10,9 @@ import static android.text.format.DateFormat.getTimeFormat;
/**
* Created by Phillip Hsu on 6/3/2016.
*/
public final class DateFormatUtils {
public final class TimeFormatUtils {
private DateFormatUtils() {}
private TimeFormatUtils() {}
public static String formatTime(Context context, long millis) {
return getTimeFormat(context).format(new Date(millis));

View File

@ -1,4 +1,4 @@
package com.philliphsu.clock2.editalarm;
package com.philliphsu.clock2.util;
import android.text.SpannableString;
import android.text.Spanned;

View File

@ -1,225 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".editalarm.EditAlarmActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="@color/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
android:layout_alignParentTop="true"
android:elevation="4dp"/>
<LinearLayout
android:id="@+id/footer_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:buttonBarStyle"
android:elevation="4dp"
android:layoutDirection="rtl"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save"
android:textColor="@color/colorAccent"
style="?borderlessButtonStyle"/>
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/delete"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Button"
style="?borderlessButtonStyle"/>
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/toolbar"
android:layout_above="@id/footer_buttons">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.SwitchCompat
android:id="@+id/on_off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"/>
<!-- TODO: Find out how to get a touch ripple; foreground attr didn't work -->
<EditText
android:id="@+id/input_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
android:background="@android:color/transparent"
android:hint="@string/default_alarm_time_12h"
android:textSize="56sp"
android:layout_toStartOf="@id/on_off"
android:inputType="time"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<ToggleButton
android:id="@+id/day0"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"/>
<ToggleButton
android:id="@+id/day1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day6"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
</LinearLayout>
<EditText
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="72dp"
android:background="@android:color/transparent"
android:hint="Add label"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<Button
android:id="@+id/ringtone"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:text="Ringtone"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="18sp"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/vibrate"/>
<CheckBox
android:id="@id/vibrate"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Vibrate"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
</LinearLayout>
</ScrollView>
<com.philliphsu.clock2.editalarm.AlarmNumpad
android:id="@+id/numpad"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:elevation="4dp"
android:layout_alignParentBottom="true"
android:visibility="gone"/>
</RelativeLayout>

View File

@ -1,226 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- For elevation to apply, we set a non-transparent background -->
<LinearLayout
android:id="@+id/footer_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?android:colorBackground"
android:elevation="4dp"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/delete"
style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
<Button
android:id="@+id/save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/save"
style="@style/Widget.AppCompat.Button.Borderless.Colored"/>
</LinearLayout>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/footer_buttons"
android:layout_alignParentTop="true">
<!-- We don't intend to use any scroll flags, but this is needed
so we can properly position our scrolling content below the appbar -->
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
android:elevation="4dp">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="@color/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.SwitchCompat
android:id="@+id/on_off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"/>
<TextView
android:id="@+id/input_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:hint="@string/default_alarm_time_12h"
android:textSize="56sp"
android:layout_toStartOf="@id/on_off"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<ToggleButton
android:id="@+id/day0"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"/>
<ToggleButton
android:id="@+id/day1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
<ToggleButton
android:id="@+id/day6"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAllCaps="true"
android:textStyle="bold"
android:background="?selectableItemBackground"
android:textColor="@color/toggle_alarm_days"
/>
</LinearLayout>
<EditText
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="72dp"
android:background="@android:color/transparent"
android:hint="Add label"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<Button
android:id="@+id/ringtone"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:text="Ringtone"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="18sp"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/vibrate"/>
<CheckBox
android:id="@id/vibrate"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Vibrate"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
</RelativeLayout>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.philliphsu.clock2.editalarm.EditAlarmActivity"
tools:showIn="@layout/activity_edit_alarm">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/large_text"/>
</android.support.v4.widget.NestedScrollView>

View File

@ -1,17 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.philliphsu.clock2.editalarm.EditAlarmActivity">
<item
android:id="@+id/action_dismiss_now"
android:title="@string/dismiss_now"
android:icon="@android:drawable/ic_delete"
app:showAsAction="ifRoom"
android:visible="false"/>
<item
android:id="@+id/action_done_snoozing"
android:title="@string/done_snoozing"
android:icon="@android:drawable/ic_delete"
app:showAsAction="ifRoom"
android:visible="false"/>
</menu>