blob: 298272a843fc776c402e06eda96dbfcf59fba784 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock.timer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroupOverlay;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.android.deskclock.CircleButtonsLayout;
import com.android.deskclock.DeskClock;
import com.android.deskclock.DeskClock.OnTapListener;
import com.android.deskclock.DeskClockFragment;
import com.android.deskclock.LabelDialogFragment;
import com.android.deskclock.LogUtils;
import com.android.deskclock.R;
import com.android.deskclock.TimerSetupView;
import com.android.deskclock.Utils;
import com.android.deskclock.widget.sgv.GridAdapter;
import com.android.deskclock.widget.sgv.SgvAnimationHelper.AnimationIn;
import com.android.deskclock.widget.sgv.SgvAnimationHelper.AnimationOut;
import com.android.deskclock.widget.sgv.StaggeredGridView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
// TODO: This class is renamed from TimerFragment to TimerFullScreenFragment with no change. It
// is responsible for the timer list in full screen timer alert and should be deprecated shortly.
public class TimerFullScreenFragment extends DeskClockFragment
implements OnClickListener, OnSharedPreferenceChangeListener {
private static final String TAG = "TimerFragment1";
private static final String KEY_ENTRY_STATE = "entry_state";
private static final Interpolator REVEAL_INTERPOLATOR =
new PathInterpolator(0.0f, 0.0f, 0.2f, 1.0f);
public static final String GOTO_SETUP_VIEW = "deskclock.timers.gotosetup";
private Bundle mViewState;
private StaggeredGridView mTimersList;
private View mTimersListPage;
private int mColumnCount;
private ImageButton mFab;
private TimerSetupView mTimerSetup;
private TimersListAdapter mAdapter;
private boolean mTicking = false;
private SharedPreferences mPrefs;
private NotificationManager mNotificationManager;
private OnEmptyListListener mOnEmptyListListener;
private View mLastVisibleView = null; // used to decide if to set the view or animate to it.
class ClickAction {
public static final int ACTION_STOP = 1;
public static final int ACTION_PLUS_ONE = 2;
public static final int ACTION_DELETE = 3;
public int mAction;
public TimerObj mTimer;
public ClickAction(int action, TimerObj t) {
mAction = action;
mTimer = t;
}
}
// Container Activity that requests TIMESUP_MODE must implement this interface
public interface OnEmptyListListener {
public void onEmptyList();
public void onListChanged();
}
TimersListAdapter createAdapter(Context context, SharedPreferences prefs) {
if (mOnEmptyListListener == null) {
return new TimersListAdapter(context, prefs);
} else {
return new TimesUpListAdapter(context, prefs);
}
}
private class TimersListAdapter extends GridAdapter {
ArrayList<TimerObj> mTimers = new ArrayList<TimerObj>();
Context mContext;
SharedPreferences mmPrefs;
private void clear() {
mTimers.clear();
notifyDataSetChanged();
}
public TimersListAdapter(Context context, SharedPreferences prefs) {
mContext = context;
mmPrefs = prefs;
}
@Override
public int getCount() {
return mTimers.size();
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public TimerObj getItem(int p) {
return mTimers.get(p);
}
@Override
public long getItemId(int p) {
if (p >= 0 && p < mTimers.size()) {
return mTimers.get(p).mTimerId;
}
return 0;
}
public void deleteTimer(int id) {
for (int i = 0; i < mTimers.size(); i++) {
TimerObj t = mTimers.get(i);
if (t.mTimerId == id) {
if (t.mView != null) {
((TimerListItem) t.mView).stop();
}
t.deleteFromSharedPref(mmPrefs);
mTimers.remove(i);
if (mTimers.size() == 1 && mColumnCount > 1) {
// If we're going from two timers to one (in the same row), we don't want to
// animate the translation because we're changing the layout params span
// from 1 to 2, and the animation doesn't handle that very well. So instead,
// just fade out and in.
mTimersList.setAnimationMode(AnimationIn.FADE, AnimationOut.FADE);
} else {
mTimersList.setAnimationMode(
AnimationIn.FLY_IN_NEW_VIEWS, AnimationOut.FADE);
}
notifyDataSetChanged();
return;
}
}
}
protected int findTimerPositionById(int id) {
for (int i = 0; i < mTimers.size(); i++) {
TimerObj t = mTimers.get(i);
if (t.mTimerId == id) {
return i;
}
}
return -1;
}
public void removeTimer(TimerObj timerObj) {
int position = findTimerPositionById(timerObj.mTimerId);
if (position >= 0) {
mTimers.remove(position);
notifyDataSetChanged();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
final TimerListItem v = (TimerListItem) inflater.inflate(R.layout.timer_list_item,
null);
final TimerObj o = (TimerObj) getItem(position);
o.mView = v;
long timeLeft = o.updateTimeLeft(false);
boolean drawRed = o.mState != TimerObj.STATE_RESTART;
v.set(o.mOriginalLength, timeLeft, drawRed);
v.setTime(timeLeft, true);
switch (o.mState) {
case TimerObj.STATE_RUNNING:
v.start();
break;
case TimerObj.STATE_TIMESUP:
v.timesUp();
break;
case TimerObj.STATE_DONE:
v.done();
break;
default:
break;
}
// Timer text serves as a virtual start/stop button.
final CountingTimerView countingTimerView = (CountingTimerView)
v.findViewById(R.id.timer_time_text);
countingTimerView.registerVirtualButtonAction(new Runnable() {
@Override
public void run() {
TimerFullScreenFragment.this.onClickHelper(
new ClickAction(ClickAction.ACTION_STOP, o));
}
});
CircleButtonsLayout circleLayout =
(CircleButtonsLayout) v.findViewById(R.id.timer_circle);
circleLayout.setCircleTimerViewIds(R.id.timer_time, R.id.reset_add, R.id.timer_label,
R.id.timer_label_text);
ImageButton resetAddButton = (ImageButton) v.findViewById(R.id.reset_add);
resetAddButton.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o));
v.setResetAddButton(true, TimerFullScreenFragment.this);
FrameLayout label = (FrameLayout) v.findViewById(R.id.timer_label);
TextView labelIcon = (TextView) v.findViewById(R.id.timer_label_placeholder);
TextView labelText = (TextView) v.findViewById(R.id.timer_label_text);
if (o.mLabel.equals("")) {
labelText.setVisibility(View.GONE);
labelIcon.setVisibility(View.VISIBLE);
} else {
labelText.setText(o.mLabel);
labelText.setVisibility(View.VISIBLE);
labelIcon.setVisibility(View.GONE);
}
if (getActivity() instanceof DeskClock) {
label.setOnTouchListener(new OnTapListener(getActivity(), labelText) {
@Override
protected void processClick(View v) {
onLabelPressed(o);
}
});
} else {
labelIcon.setVisibility(View.INVISIBLE);
}
return v;
}
@Override
public int getItemColumnSpan(Object item, int position) {
// This returns the width for a specified position. If we only have one item, have it
// span all columns so that it's centered. Otherwise, all timers should just span one.
if (getCount() == 1) {
return mColumnCount;
} else {
return 1;
}
}
public void addTimer(TimerObj t) {
mTimers.add(0, t);
sort();
}
public void onSaveInstanceState(Bundle outState) {
TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
}
public void onRestoreInstanceState(Bundle outState) {
TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers);
sort();
}
public void saveGlobalState() {
TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
}
public void sort() {
if (getCount() > 0) {
Collections.sort(mTimers, mTimersCompare);
notifyDataSetChanged();
}
}
private final Comparator<TimerObj> mTimersCompare = new Comparator<TimerObj>() {
static final int BUZZING = 0;
static final int IN_USE = 1;
static final int NOT_USED = 2;
protected int getSection(TimerObj timerObj) {
switch (timerObj.mState) {
case TimerObj.STATE_TIMESUP:
return BUZZING;
case TimerObj.STATE_RUNNING:
case TimerObj.STATE_STOPPED:
return IN_USE;
default:
return NOT_USED;
}
}
@Override
public int compare(TimerObj o1, TimerObj o2) {
int section1 = getSection(o1);
int section2 = getSection(o2);
if (section1 != section2) {
return (section1 < section2) ? -1 : 1;
} else if (section1 == BUZZING || section1 == IN_USE) {
return (o1.mTimeLeft < o2.mTimeLeft) ? -1 : 1;
} else {
return (o1.mSetupLength < o2.mSetupLength) ? -1 : 1;
}
}
};
}
private class TimesUpListAdapter extends TimersListAdapter {
public TimesUpListAdapter(Context context, SharedPreferences prefs) {
super(context, prefs);
}
@Override
public void onSaveInstanceState(Bundle outState) {
// This adapter has a data subset and never updates entire database
// Individual timers are updated in button handlers.
}
@Override
public void saveGlobalState() {
// This adapter has a data subset and never updates entire database
// Individual timers are updated in button handlers.
}
@Override
public void onRestoreInstanceState(Bundle outState) {
// This adapter loads a subset
TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP);
if (getCount() == 0) {
mOnEmptyListListener.onEmptyList();
} else {
Collections.sort(mTimers, new Comparator<TimerObj>() {
@Override
public int compare(TimerObj o1, TimerObj o2) {
return (o1.mTimeLeft < o2.mTimeLeft) ? -1 : 1;
}
});
}
}
}
private final Runnable mClockTick = new Runnable() {
boolean mVisible = true;
final static int TIME_PERIOD_MS = 1000;
final static int SPLIT = TIME_PERIOD_MS / 2;
@Override
public void run() {
// Setup for blinking
boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT;
boolean toggle = mVisible != visible;
mVisible = visible;
for (int i = 0; i < mAdapter.getCount(); i++) {
TimerObj t = mAdapter.getItem(i);
if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) {
long timeLeft = t.updateTimeLeft(false);
if (t.mView != null) {
((TimerListItem) (t.mView)).setTime(timeLeft, false);
}
}
if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE
&& t.mState != TimerObj.STATE_RESTART) {
t.mState = TimerObj.STATE_TIMESUP;
if (t.mView != null) {
((TimerListItem) (t.mView)).timesUp();
}
}
// The blinking
if (toggle && t.mView != null) {
if (t.mState == TimerObj.STATE_TIMESUP) {
((TimerListItem) (t.mView)).setCircleBlink(mVisible);
}
if (t.mState == TimerObj.STATE_STOPPED) {
((TimerListItem) (t.mView)).setTextBlink(mVisible);
}
}
}
mTimersList.postDelayed(mClockTick, 20);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
// Cache instance data and consume in first call to setupPage()
if (savedInstanceState != null) {
mViewState = savedInstanceState;
}
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.timer_full_screen_fragment, container, false);
// Handle arguments from parent
Bundle bundle = getArguments();
if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) {
if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) {
try {
mOnEmptyListListener = (OnEmptyListListener) getActivity();
} catch (ClassCastException e) {
Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener");
}
}
}
mFab = (ImageButton) v.findViewById(R.id.fab);
mTimersList = (StaggeredGridView) v.findViewById(R.id.timers_list);
// For tablets in landscape, the count will be 2. All else will be 1.
mColumnCount = getResources().getInteger(R.integer.timer_column_count);
mTimersList.setColumnCount(mColumnCount);
// Set this to true; otherwise adding new views to the end of the list won't cause
// everything above it to be filled in correctly.
mTimersList.setGuardAgainstJaggedEdges(true);
mTimersListPage = v.findViewById(R.id.timers_list_page);
mTimerSetup = (TimerSetupView) v.findViewById(R.id.timer_setup);
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
mNotificationManager = (NotificationManager)
getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
return v;
}
@Override
public void onDestroyView() {
mViewState = new Bundle();
saveViewState(mViewState);
super.onDestroyView();
}
@Override
public void onResume() {
Intent newIntent = null;
if (getActivity() instanceof DeskClock) {
DeskClock activity = (DeskClock) getActivity();
activity.registerPageChangedListener(this);
newIntent = activity.getIntent();
}
super.onResume();
mPrefs.registerOnSharedPreferenceChangeListener(this);
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
LayoutParams params;
float dividerHeight = getResources().getDimension(R.dimen.timer_divider_height);
if (getActivity() instanceof DeskClock) {
// If this is a DeskClock fragment (i.e. not a FullScreenTimerAlert), add a footer to
// the bottom of the list so that it can scroll underneath the bottom button bar.
// StaggeredGridView doesn't support a footer view, but GridAdapter does, so this
// can't happen until the Adapter itself is instantiated.
View footerView = getActivity().getLayoutInflater().inflate(
R.layout.blank_footer_view, mTimersList, false);
params = footerView.getLayoutParams();
params.height -= dividerHeight;
footerView.setLayoutParams(params);
mAdapter.setFooterView(footerView);
}
if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) {
// Clear the flag set in the notification because the adapter was just
// created and is thus in sync with the database
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_NOTIFICATION, false);
editor.apply();
}
if (mPrefs.getBoolean(Timers.FROM_ALERT, false)) {
// Clear the flag set in the alert because the adapter was just
// created and is thus in sync with the database
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_ALERT, false);
editor.apply();
}
mTimersList.setAdapter(mAdapter);
mLastVisibleView = null; // Force a non animation setting of the view
setPage();
// View was hidden in onPause, make sure it is visible now.
View v = getView();
if (v != null) {
getView().setVisibility(View.VISIBLE);
}
if (newIntent != null) {
processIntent(newIntent);
}
mFab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
revealAnimation(mFab, getActivity().getResources().getColor(R.color.clock_white));
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
updateAllTimesUpTimers(false /* stop */);
}
}, TimerFragment.ANIMATION_TIME_MILLIS);
}
});
}
private void revealAnimation(final View centerView, int color) {
final Activity activity = getActivity();
final View decorView = activity.getWindow().getDecorView();
final ViewGroupOverlay overlay = (ViewGroupOverlay) decorView.getOverlay();
// Create a transient view for performing the reveal animation.
final View revealView = new View(activity);
revealView.setRight(decorView.getWidth());
revealView.setBottom(decorView.getHeight());
revealView.setBackgroundColor(color);
overlay.add(revealView);
final int[] clearLocation = new int[2];
centerView.getLocationInWindow(clearLocation);
clearLocation[0] += centerView.getWidth() / 2;
clearLocation[1] += centerView.getHeight() / 2;
final int revealCenterX = clearLocation[0] - revealView.getLeft();
final int revealCenterY = clearLocation[1] - revealView.getTop();
final int xMax = Math.max(revealCenterX, decorView.getWidth() - revealCenterX);
final int yMax = Math.max(revealCenterY, decorView.getHeight() - revealCenterY);
final float revealRadius = (float) Math.sqrt(Math.pow(xMax, 2.0) + Math.pow(yMax, 2.0));
final Animator revealAnimator = ViewAnimationUtils.createCircularReveal(
revealView, revealCenterX, revealCenterY, 0.0f, revealRadius);
revealAnimator.setInterpolator(REVEAL_INTERPOLATOR);
final ValueAnimator fadeAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 1.0f);
fadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
overlay.remove(revealView);
}
});
final AnimatorSet alertAnimator = new AnimatorSet();
alertAnimator.setDuration(TimerFragment.ANIMATION_TIME_MILLIS);
alertAnimator.play(revealAnimator).before(fadeAnimator);
alertAnimator.start();
}
@Override
public void onPause() {
if (getActivity() instanceof DeskClock) {
((DeskClock) getActivity()).unregisterPageChangedListener(this);
}
super.onPause();
stopClockTicks();
if (mAdapter != null) {
mAdapter.saveGlobalState();
}
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
// This is called because the lock screen was activated, the window stay
// active under it and when we unlock the screen, we see the old time for
// a fraction of a second.
View v = getView();
if (v != null) {
v.setVisibility(View.INVISIBLE);
}
}
@Override
public void onPageChanged(int page) {
if (page == DeskClock.TIMER_TAB_INDEX && mAdapter != null) {
mAdapter.sort();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mAdapter != null) {
mAdapter.onSaveInstanceState(outState);
}
if (mTimerSetup != null) {
saveViewState(outState);
} else if (mViewState != null) {
outState.putAll(mViewState);
}
}
private void saveViewState(Bundle outState) {
mTimerSetup.saveEntryState(outState, KEY_ENTRY_STATE);
}
public void setPage() {
boolean switchToSetupView;
if (mViewState != null) {
switchToSetupView = false;
mTimerSetup.restoreEntryState(mViewState, KEY_ENTRY_STATE);
mViewState = null;
} else {
switchToSetupView = mAdapter.getCount() == 0;
}
if (switchToSetupView) {
gotoSetupView();
} else {
gotoTimersView();
}
}
private void resetTimer(TimerObj t) {
t.mState = TimerObj.STATE_RESTART;
t.mTimeLeft = t.mOriginalLength = t.mSetupLength;
// when multiple timers are firing, some timers will be off-screen and they will not
// have Fragment instances unless user scrolls down further. t.mView is null in this case.
if (t.mView != null) {
t.mView.stop();
t.mView.setTime(t.mTimeLeft, false);
t.mView.set(t.mOriginalLength, t.mTimeLeft, false);
}
updateTimersState(t, Timers.TIMER_RESET);
}
public void updateAllTimesUpTimers(boolean stop) {
boolean notifyChange = false;
// To avoid race conditions where a timer was dismissed and it is still in the timers list
// and can be picked again, create a temporary list of timers to be removed first and
// then removed them one by one
LinkedList<TimerObj> timesupTimers = new LinkedList<TimerObj>();
for (int i = 0; i < mAdapter.getCount(); i++) {
TimerObj timerObj = mAdapter.getItem(i);
if (timerObj.mState == TimerObj.STATE_TIMESUP) {
timesupTimers.addFirst(timerObj);
notifyChange = true;
}
}
while (timesupTimers.size() > 0) {
final TimerObj t = timesupTimers.remove();
if (stop) {
onStopButtonPressed(t);
} else {
resetTimer(t);
}
}
if (notifyChange) {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(Timers.FROM_ALERT, true);
editor.apply();
}
}
private void gotoSetupView() {
if (mLastVisibleView == null || mLastVisibleView.getId() == R.id.timer_setup) {
mTimerSetup.setVisibility(View.VISIBLE);
mTimerSetup.setScaleX(1f);
mTimersListPage.setVisibility(View.GONE);
} else {
// Animate
ObjectAnimator a = ObjectAnimator.ofFloat(mTimersListPage, View.SCALE_X, 1f, 0f);
a.setInterpolator(new AccelerateInterpolator());
a.setDuration(125);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTimersListPage.setVisibility(View.GONE);
mTimerSetup.setScaleX(0);
mTimerSetup.setVisibility(View.VISIBLE);
ObjectAnimator b = ObjectAnimator.ofFloat(mTimerSetup, View.SCALE_X, 0f, 1f);
b.setInterpolator(new DecelerateInterpolator());
b.setDuration(225);
b.start();
}
});
a.start();
}
stopClockTicks();
mTimerSetup.updateDeleteButtonAndDivider();
mLastVisibleView = mTimerSetup;
}
private void gotoTimersView() {
if (mLastVisibleView == null || mLastVisibleView.getId() == R.id.timers_list_page) {
mTimerSetup.setVisibility(View.GONE);
mTimersListPage.setVisibility(View.VISIBLE);
mTimersListPage.setScaleX(1f);
} else {
// Animate
ObjectAnimator a = ObjectAnimator.ofFloat(mTimerSetup, View.SCALE_X, 1f, 0f);
a.setInterpolator(new AccelerateInterpolator());
a.setDuration(125);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTimerSetup.setVisibility(View.GONE);
mTimersListPage.setScaleX(0);
mTimersListPage.setVisibility(View.VISIBLE);
ObjectAnimator b =
ObjectAnimator.ofFloat(mTimersListPage, View.SCALE_X, 0f, 1f);
b.setInterpolator(new DecelerateInterpolator());
b.setDuration(225);
b.start();
}
});
a.start();
}
startClockTicks();
mLastVisibleView = mTimersListPage;
}
@Override
public void onClick(View v) {
ClickAction tag = (ClickAction) v.getTag();
onClickHelper(tag);
}
private void onClickHelper(ClickAction clickAction) {
switch (clickAction.mAction) {
case ClickAction.ACTION_DELETE:
final TimerObj t = clickAction.mTimer;
if (t.mState == TimerObj.STATE_TIMESUP) {
cancelTimerNotification(t.mTimerId);
}
// Tell receiver the timer was deleted.
// It will stop all activity related to the
// timer
t.mState = TimerObj.STATE_DELETED;
updateTimersState(t, Timers.DELETE_TIMER);
break;
case ClickAction.ACTION_PLUS_ONE:
onPlusOneButtonPressed(clickAction.mTimer);
break;
case ClickAction.ACTION_STOP:
onStopButtonPressed(clickAction.mTimer);
break;
default:
break;
}
}
private void onPlusOneButtonPressed(TimerObj t) {
switch (t.mState) {
case TimerObj.STATE_RUNNING:
t.addTime(TimerObj.MINUTE_IN_MILLIS);
long timeLeft = t.updateTimeLeft(false);
((TimerListItem) (t.mView)).setTime(timeLeft, false);
((TimerListItem) (t.mView)).setLength(timeLeft);
mAdapter.notifyDataSetChanged();
updateTimersState(t, Timers.TIMER_UPDATE);
break;
case TimerObj.STATE_TIMESUP:
// +1 min when the time is up will restart the timer with 1 minute left.
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow();
t.mTimeLeft = t.mOriginalLength = TimerObj.MINUTE_IN_MILLIS;
updateTimersState(t, Timers.TIMER_RESET);
updateTimersState(t, Timers.START_TIMER);
updateTimesUpMode(t);
cancelTimerNotification(t.mTimerId);
break;
case TimerObj.STATE_STOPPED:
case TimerObj.STATE_DONE:
t.mState = TimerObj.STATE_RESTART;
t.mTimeLeft = t.mOriginalLength = t.mSetupLength;
((TimerListItem) t.mView).stop();
((TimerListItem) t.mView).setTime(t.mTimeLeft, false);
((TimerListItem) t.mView).set(t.mOriginalLength, t.mTimeLeft, false);
updateTimersState(t, Timers.TIMER_RESET);
break;
default:
break;
}
}
private void onStopButtonPressed(TimerObj t) {
switch (t.mState) {
case TimerObj.STATE_RUNNING:
// Stop timer and save the remaining time of the timer
t.mState = TimerObj.STATE_STOPPED;
((TimerListItem) t.mView).pause();
t.updateTimeLeft(true);
updateTimersState(t, Timers.TIMER_STOP);
break;
case TimerObj.STATE_STOPPED:
// Reset the remaining time and continue timer
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
((TimerListItem) t.mView).start();
updateTimersState(t, Timers.START_TIMER);
break;
case TimerObj.STATE_TIMESUP:
if (t.mDeleteAfterUse) {
cancelTimerNotification(t.mTimerId);
// Tell receiver the timer was deleted.
// It will stop all activity related to the
// timer
t.mState = TimerObj.STATE_DELETED;
updateTimersState(t, Timers.DELETE_TIMER);
} else {
t.mState = TimerObj.STATE_DONE;
// Used in a context where the timer could be off-screen and without a view
if (t.mView != null) {
((TimerListItem) t.mView).done();
}
updateTimersState(t, Timers.TIMER_DONE);
cancelTimerNotification(t.mTimerId);
updateTimesUpMode(t);
}
break;
case TimerObj.STATE_DONE:
break;
case TimerObj.STATE_RESTART:
t.mState = TimerObj.STATE_RUNNING;
t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
((TimerListItem) t.mView).start();
updateTimersState(t, Timers.START_TIMER);
break;
default:
break;
}
}
private void onLabelPressed(TimerObj t) {
final FragmentTransaction ft = getFragmentManager().beginTransaction();
final Fragment prev = getFragmentManager().findFragmentByTag("label_dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
// Create and show the dialog.
final LabelDialogFragment newFragment =
LabelDialogFragment.newInstance(t, t.mLabel, getTag());
newFragment.show(ft, "label_dialog");
}
// Starts the ticks that animate the timers.
private void startClockTicks() {
mTimersList.postDelayed(mClockTick, 20);
mTicking = true;
}
// Stops the ticks that animate the timers.
private void stopClockTicks() {
if (mTicking) {
mTimersList.removeCallbacks(mClockTick);
mTicking = false;
}
}
private void updateTimersState(TimerObj t, String action) {
if (Timers.DELETE_TIMER.equals(action)) {
LogUtils.e("~~ update timer state");
t.deleteFromSharedPref(mPrefs);
} else {
t.writeToSharedPref(mPrefs);
}
Intent i = new Intent();
i.setAction(action);
i.putExtra(Timers.TIMER_INTENT_EXTRA, t.mTimerId);
// Make sure the receiver is getting the intent ASAP.
i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
getActivity().sendBroadcast(i);
}
private void cancelTimerNotification(int timerId) {
mNotificationManager.cancel(timerId);
}
private void updateTimesUpMode(TimerObj timerObj) {
if (mOnEmptyListListener != null && timerObj.mState != TimerObj.STATE_TIMESUP) {
mAdapter.removeTimer(timerObj);
if (mAdapter.getCount() == 0) {
mOnEmptyListListener.onEmptyList();
} else {
mOnEmptyListListener.onListChanged();
}
}
}
public void restartAdapter() {
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
}
// Process extras that were sent to the app and were intended for the timer
// fragment
public void processIntent(Intent intent) {
// switch to timer setup view
if (intent.getBooleanExtra(GOTO_SETUP_VIEW, false)) {
gotoSetupView();
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (prefs.equals(mPrefs)) {
if ((key.equals(Timers.FROM_ALERT) && prefs.getBoolean(Timers.FROM_ALERT, false))
|| (key.equals(Timers.FROM_NOTIFICATION)
&& prefs.getBoolean(Timers.FROM_NOTIFICATION, false))) {
// The data-changed flag was set in the alert or notification so the adapter needs
// to re-sync with the database
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(key, false);
editor.apply();
mAdapter = createAdapter(getActivity(), mPrefs);
mAdapter.onRestoreInstanceState(null);
mTimersList.setAdapter(mAdapter);
}
}
}
@Override
public void onFabClick(View view) {
if (mLastVisibleView != mTimersListPage) {
// New timer create if timer length is not zero
// Create a new timer object to track the timer and
// switch to the timers view.
int timerLength = mTimerSetup.getTime();
if (timerLength == 0) {
return;
}
TimerObj t = new TimerObj(timerLength * DateUtils.SECOND_IN_MILLIS, getActivity());
t.mState = TimerObj.STATE_RUNNING;
mAdapter.addTimer(t);
updateTimersState(t, Timers.START_TIMER);
gotoTimersView();
mTimerSetup.reset(); // Make sure the setup is cleared for next time
mTimersList.setFirstPositionAndOffsets(
mAdapter.findTimerPositionById(t.mTimerId), 0);
} else {
mTimerSetup.reset();
gotoSetupView();
}
}
}