/* * Copyright (C) 2007 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 net.redgeek.android.eventrend.calendar; import android.app.AlertDialog; import android.app.Dialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.view.GestureDetector; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import net.redgeek.android.eventrend.EvenTrendActivity; import net.redgeek.android.eventrend.Preferences; import net.redgeek.android.eventrend.R; import net.redgeek.android.eventrend.db.CategoryDbTable; import net.redgeek.android.eventrend.db.EvenTrendDbAdapter; import net.redgeek.android.eventrend.graph.GraphActivity; import net.redgeek.android.eventrend.primitives.Datapoint; import net.redgeek.android.eventrend.primitives.TimeSeries; import net.redgeek.android.eventrend.primitives.TimeSeriesCollector; import net.redgeek.android.eventrend.util.DateUtil; import net.redgeek.android.eventrend.util.DynamicSpinner; import net.redgeek.android.eventrend.util.Number; import net.redgeek.android.eventrend.util.ProgressIndicator; import net.redgeek.android.eventrend.util.DateUtil.Period; import java.util.ArrayList; import java.util.Calendar; public class CalendarActivity extends EvenTrendActivity { // Menu items private static final int MENU_CALENDAR_RANGE_ID = Menu.FIRST; private static final int MENU_CALENDAR_GRAPH_ID = Menu.FIRST + 1; private static final int MENU_CALENDAR_PREFS_ID = Menu.FIRST + 2; private static final int MENU_HELP_ID = Menu.FIRST + 3; // Dialogs private static final int DIALOG_CALENDAR_FILTER = 0; private static final int DIALOG_RANGE_INFO = 1; private static final int HELP_DIALOG_ID = 2; // UI elements private LinearLayout mCalendarControls; private LinearLayout mCalendarViewLayout; private TextView mCalendarStatus; private CalendarView mCalendarView; private DynamicSpinner mCategorySpinner; private DynamicSpinner mPeriodSpinner; private Dialog mFilterDialog; private ProgressIndicator.Titlebar mProgress; // Listeners private Spinner.OnItemSelectedListener mAggregationSpinnerListener; private Spinner.OnItemSelectedListener mCategorySpinnerListener; private Spinner.OnItemSelectedListener mPeriodSpinnerListener; private GestureDetector mGestureDetector; private OnTouchListener mTouchListener; // Private data private TimeSeriesCollector mTSC; private ArrayList<Integer> mSeriesEnabled; private int mHistory; private float mSmoothing; private float mSensitivity; private int mDecimals; private Period mPeriod; // Saved across orientation changes private long mStartMs; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); getPrefs(); setupData(icicle); setupUI(); populateFields(); } private void getPrefs() { mHistory = Preferences.getHistory(getCtx()); mSmoothing = Preferences.getSmoothingConstant(getCtx()); mDecimals = Preferences.getDecimalPlaces(getCtx()); mSensitivity = Preferences.getStdDevSensitivity(getCtx()); } private void setupData(Bundle icicle) { mTSC = new TimeSeriesCollector(getDbh()); mTSC.setHistory(mHistory); mTSC.setSmoothing(mSmoothing); mTSC.setSensitivity(mSensitivity); mTSC.setInterpolators(((EvenTrendActivity) getCtx()).getInterpolators()); mTSC.updateTimeSeriesMetaLocking(true); mSeriesEnabled = getIntent().getIntegerArrayListExtra(VIEW_DEFAULT_CATIDS); if (mSeriesEnabled != null) { for (int i = 0; i < mSeriesEnabled.size(); i++) { Integer j = mSeriesEnabled.get(i); mTSC.setSeriesEnabled(j.longValue(), true); break; } } Calendar cal = Calendar.getInstance(); mStartMs = getIntent().getLongExtra(GRAPH_START_MS, cal.getTimeInMillis()); long ms = getIntent().getLongExtra(CALENDAR_PERIOD, DateUtil.MONTH_MS); mPeriod = DateUtil.mapLongToPeriod(ms); if (icicle != null) { mStartMs = icicle.getLong(GRAPH_START_MS); ms = icicle.getLong(CALENDAR_PERIOD); mPeriod = DateUtil.mapLongToPeriod(ms); } } private void setupUI() { setupListeners(); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.calendar_view); mCalendarStatus = (TextView) findViewById(R.id.calendar_status_text); mCalendarView = new CalendarView(this, mTSC); mCalendarView.resetZoom(); mCalendarView.setFadingEdgeLength(CalendarView.BORDER_Y * 2); mCalendarView.setVerticalFadingEdgeEnabled(true); mCalendarView.setPeriod(mPeriod); mCalendarViewLayout = (LinearLayout) findViewById(R.id.calendar_plot); mCalendarViewLayout.addView(mCalendarView); mCalendarControls = (LinearLayout) findViewById(R.id.calendar_controls); mCategorySpinner = new DynamicSpinner(getCtx()); mCategorySpinner.setPrompt("Category"); mCalendarControls.addView(mCategorySpinner, new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); Cursor c = getDbh().fetchAllCategories(); c.moveToFirst(); for (int i = 0; i < c.getCount(); i++) { long id = CategoryDbTable.getId(c); String name = CategoryDbTable.getCategoryName(c); mCategorySpinner.addSpinnerItem(name, new Long(id)); if (id == mSeriesEnabled.get(0)) { mCategorySpinner.setSelection(i); } c.moveToNext(); } c.close(); mCategorySpinner.setOnItemSelectedListener(mCategorySpinnerListener); mPeriodSpinner = new DynamicSpinner(getCtx()); mPeriodSpinner.setPrompt("View"); mCalendarControls.addView(mPeriodSpinner, new LinearLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); mPeriodSpinner.addSpinnerItem(CategoryDbTable.KEY_PERIOD_MONTH, new Long(1)); mPeriodSpinner.addSpinnerItem(CategoryDbTable.KEY_PERIOD_YEAR, new Long(2)); if (mPeriod == Period.MONTH) mPeriodSpinner.setSelection(0); else if(mPeriod == Period.YEAR) mPeriodSpinner.setSelection(1); mPeriodSpinner.setOnItemSelectedListener(mPeriodSpinnerListener); mProgress = new ProgressIndicator.Titlebar(getCtx()); setupGestures(); } private void setupListeners() { mCategorySpinnerListener = new Spinner.OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View v, int position, long id) { ArrayList<TimeSeries> series = mTSC.getAllSeries(); for (int i = 0; i < series.size(); i++) { setSeriesEnabled(series.get(i).getDbRow().getId(), false); } long catId = mCategorySpinner.getMappingFromPosition(position); mCalendarView.setCategoryId(catId); setSeriesEnabled(catId, true); display(); return; } public void onNothingSelected(AdapterView arg0) { return; } }; mPeriodSpinnerListener = new Spinner.OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View v, int position, long id) { String period = ((TextView) v).getText().toString(); if (period.equals(CategoryDbTable.KEY_PERIOD_YEAR)) { mPeriod = Period.YEAR; mCalendarView.setPeriod(mPeriod); mTSC.setAggregationMs(DateUtil.mapPeriodToLong(Period.MONTH)); } else if (period.equals(CategoryDbTable.KEY_PERIOD_MONTH)) { mPeriod = Period.MONTH; mCalendarView.setPeriod(mPeriod); mTSC.setAggregationMs(DateUtil.mapPeriodToLong(Period.DAY)); } display(); return; } public void onNothingSelected(AdapterView arg0) { return; } }; } public void setupGestures() { mGestureDetector = new GestureDetector( new GestureDetector.SimpleOnGestureListener() { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float deltaX = e2.getRawX() - e1.getRawX(); float deltaY = e2.getRawY() - e1.getRawY(); int minSlideWidth = mCalendarView.getWidth() / 2; int minSlideHeight = mCalendarView.getHeight() / 2; if (Math.abs(deltaX) < 100) { if (deltaY > minSlideHeight) { slideDown(); return true; } if (deltaY < -minSlideHeight) { slideUp(); return true; } } if (Math.abs(deltaY) < 100) { if (deltaX > minSlideWidth) { slideRight(); return true; } if (deltaX < -minSlideWidth) { slideLeft(); return true; } } return false; } }); mGestureDetector.setIsLongpressEnabled(true); mTouchListener = new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } }; mCalendarView.setLongClickable(true); mCalendarView.setOnTouchListener(mTouchListener); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { mGestureDetector.onTouchEvent(ev); return super.dispatchTouchEvent(ev); } private void populateFields() { mCalendarView.getCalendar().setCalendarStart(mStartMs); mTSC.setAutoAggregation(false); } @Override public boolean onCreateOptionsMenu(Menu menu) { boolean result = super.onCreateOptionsMenu(menu); menu.add(0, MENU_CALENDAR_RANGE_ID, 0, R.string.menu_calendar_range) .setIcon(android.R.drawable.ic_menu_info_details); menu.add(0, MENU_CALENDAR_GRAPH_ID, 0, R.string.menu_graph).setIcon( R.drawable.graph); menu.add(0, MENU_CALENDAR_PREFS_ID, 0, R.string.menu_app_prefs).setIcon( android.R.drawable.ic_menu_preferences); menu.add(0, MENU_HELP_ID, 0, R.string.menu_app_help).setIcon( android.R.drawable.ic_menu_help); return result; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_CALENDAR_RANGE_ID: // TODO: implement showDialog(DIALOG_RANGE_INFO); return true; case MENU_CALENDAR_GRAPH_ID: // TODO: implement graph(); return true; case MENU_CALENDAR_PREFS_ID: editPrefs(); return true; case MENU_HELP_ID: showDialog(HELP_DIALOG_ID); return true; } return super.onOptionsItemSelected(item); } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_RANGE_INFO: return rangeInfoDialog(mCalendarView.getCategoryId()); case HELP_DIALOG_ID: String str = getResources().getString(R.string.calendar_overview); return getDialogUtil().newOkDialog("Help", str); } return null; } private void editPrefs() { Intent i = new Intent(this, Preferences.class); startActivityForResult(i, PREFS_EDIT); } private void display() { mCalendarView.updateData(); mCalendarView.invalidate(); } public void graph() { ArrayList<Integer> catIds = new ArrayList<Integer>(); catIds.add(new Integer((int) mCalendarView.getCategoryId())); Intent i = new Intent(this, GraphActivity.class); i.putIntegerArrayListExtra(VIEW_DEFAULT_CATIDS, catIds); i.putExtra(GRAPH_START_MS, mCalendarView.getCalendar().getFocusStart()); i.putExtra(GRAPH_END_MS, mCalendarView.getCalendar().getFocusEnd()); if (mPeriod == Period.YEAR) i.putExtra(GRAPH_AGGREGATION, DateUtil.MONTH_MS); else if (mPeriod == Period.MONTH) i.putExtra(GRAPH_AGGREGATION, DateUtil.DAY_MS); startActivityForResult(i, GRAPH_VIEW); } public ArrayList<Integer> getSeriesEnabledState() { return mSeriesEnabled; } public TextView getStatusTextView() { return mCalendarStatus; } public void setSeriesEnabled(long catId, boolean enabled) { mTSC.setSeriesEnabled(catId, enabled); if (mSeriesEnabled == null) return; for (int i = 0; i < mSeriesEnabled.size(); i++) { Integer id = mSeriesEnabled.get(i); if (catId == id) { if (enabled == false) mSeriesEnabled.remove(i); return; } } if (enabled == true) mSeriesEnabled.add(new Integer((int) catId)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); display(); } @Override protected void onSaveInstanceState(Bundle outState) { if (mSeriesEnabled != null) outState.putIntegerArrayList("defaultCatIds", mSeriesEnabled); long periodMs = DateUtil.mapPeriodToLong(mPeriod); outState.putLong(GRAPH_START_MS, mCalendarView.getCalendar() .getCalendarStart()); outState.putLong(CALENDAR_PERIOD, periodMs); super.onSaveInstanceState(outState); } @Override protected void onResume() { getPrefs(); mTSC.updateTimeSeriesMetaLocking(false); mTSC.setHistory(mHistory); mCalendarView.setColorScheme(); display(); super.onResume(); } private Dialog rangeInfoDialog(long catId) { Builder b = new AlertDialog.Builder(getCtx()); EvenTrendDbAdapter dbh = ((CalendarActivity) getCtx()).getDbh(); CategoryDbTable.Row cat = dbh.fetchCategory(catId); TimeSeries ts = mTSC.getSeriesByIdLocking(catId); int decimals = Preferences.getDecimalPlaces(getCtx()); String info = "Category: " + cat.getCategoryName() + "\n"; if (ts != null) { Number.RunningStats valueStats = ts.getValueStats(); Number.RunningStats timestampStats = ts.getTimestampStats(); String tsAvgPeriod = DateUtil.toString(timestampStats.mMean); String tsAvgEntry = DateUtil.toString(timestampStats.mEntryMean); String tsVar = DateUtil.toStringSquared(timestampStats.mVar); String tsSD = DateUtil.toString(timestampStats.mStdDev); Datapoint first = ts.getFirstVisible(); Datapoint last = ts.getLastVisible(); info += "Values:\n" + " " + DateUtil.toTimestamp(first.mMillis) + " -\n" + " " + DateUtil.toTimestamp(last.mMillis) + "\n" + " Range: " + ts.getVisibleValueMin() + " - " + ts.getVisibleValueMax() + "\n" + " Average: " + Number.Round(valueStats.mMean, decimals) + "\n" + " Std Dev.: " + Number.Round(valueStats.mStdDev, decimals) + "\n" + " Variance: " + Number.Round(valueStats.mVar, decimals) + "\n" + " Trend: " + Number.Round(ts.getTrendStats().mMin, decimals) + " - " + Number.Round(ts.getTrendStats().mMax, decimals) + "\n" + "Date Goal is Reached:\n" + "Time Between Datapoints:\n" + " Avgerage: " + tsAvgPeriod + "\n" + " Std Dev.: " + tsSD + "\n" + " Variance: " + tsVar + "\n" + "Time Between Entries:\n" + " Avg/Entry: " + tsAvgEntry + "\n"; } b.setTitle("Visible Range Info"); b.setMessage(info); b.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // do nothing } }); Dialog d = b.create(); return d; } public void slideRight() { // AnimationSet set = new AnimationSet(true); // set.setStartOffset(0); // set.setDuration(500); // // Animation slideOutLeft = new TranslateAnimation( // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 1.0f, // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f // ); // slideOutLeft.setStartOffset(0); // slideOutLeft.setDuration(500); // slideOutLeft.setFillAfter(true); // set.addAnimation(slideOutLeft); // // Animation slideInRight = new TranslateAnimation( // Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f, // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f // ); // slideInRight.setStartOffset(0); // slideInRight.setDuration(500); // slideInRight.setFillAfter(true); // set.addAnimation(slideInRight); // // mCalendarView.startAnimation(set); mCalendarView.prevJump(); display(); } public void slideLeft() { mCalendarView.nextJump(); display(); } public void slideDown() { mCalendarView.prevPeriod(); display(); } public void slideUp() { mCalendarView.nextPeriod(); display(); } }