/* * ActivityDiary * * Copyright (C) 2018 Raphael Mack http://www.raphael-mack.de * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package de.rampro.activitydiary.ui.statistics; import android.app.DatePickerDialog; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.v7.preference.PreferenceManager; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.DatePicker; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.formatter.IValueFormatter; import com.github.mikephil.charting.utils.ViewPortHandler; import org.osmdroid.config.Configuration; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import de.rampro.activitydiary.R; import de.rampro.activitydiary.db.ActivityDiaryContract; import de.rampro.activitydiary.helpers.DateHelper; import de.rampro.activitydiary.helpers.TimeSpanFormatter; import de.rampro.activitydiary.ui.generic.BaseActivity; import de.rampro.activitydiary.ui.history.HistoryDetailActivity; public class StatisticsActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemSelectedListener { private static final int LOADER_ID_TIME = 0; private static final int LOADER_ID_RANGE = 1; private static final String[] PROJECTION = new String[] { ActivityDiaryContract.DiaryStats.NAME, ActivityDiaryContract.DiaryStats.COLOR, ActivityDiaryContract.DiaryStats.PORTION, ActivityDiaryContract.DiaryStats.DURATION }; private PieChart chart; private Spinner timeframeSpinner; private TextView rangeTextView; private ImageView rangeEarlierImageView; private ImageView rangeLaterImageView; private long currentDateTime; private int currentOffset = 0; private int currentRange = Calendar.WEEK_OF_YEAR; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Context ctx = getApplicationContext(); Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx)); LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View contentView = inflater.inflate(R.layout.activity_statistics, null, false); setContent(contentView); timeframeSpinner = (Spinner) findViewById(R.id.timeframeSpinner); timeframeSpinner.setOnItemSelectedListener(this); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.statistic_dropdown, android.R.layout.simple_spinner_item); timeframeSpinner.setAdapter(adapter); getLoaderManager().initLoader(LOADER_ID_TIME, null, this); chart = (PieChart) findViewById(R.id.piechart); chart.getLegend().setEnabled(false); chart.setDescription(null); chart.setHoleRadius(30.0f); chart.setTransparentCircleRadius(40.0f); rangeTextView = (TextView)findViewById(R.id.rangeTextView); rangeEarlierImageView = (ImageView)findViewById(R.id.img_earlier); rangeEarlierImageView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { currentOffset = currentOffset - 1; loadRange(currentRange, currentOffset); } }); rangeLaterImageView = (ImageView) findViewById(R.id.img_later); rangeLaterImageView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { currentOffset = currentOffset + 1; loadRange(currentRange, currentOffset); } }); currentDateTime = new Date().getTime(); mDrawerToggle.setDrawerIndicatorEnabled(false); } // Called when a new Loader needs to be created public Loader<Cursor> onCreateLoader(int id, Bundle args) { // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. if(id == LOADER_ID_TIME) { return new CursorLoader(this, ActivityDiaryContract.DiaryStats.CONTENT_URI, PROJECTION, null, null, ActivityDiaryContract.DiaryStats.SORT_ORDER_DEFAULT); }else if(id == LOADER_ID_RANGE) { long start = args.getLong("start"); long end = args.getLong("end"); Uri u = ActivityDiaryContract.DiaryStats.CONTENT_URI; u = Uri.withAppendedPath(u, Long.toString(start)); u = Uri.withAppendedPath(u, Long.toString(end)); return new CursorLoader(this, u, PROJECTION, null, null, ActivityDiaryContract.DiaryStats.PORTION + " DESC"); }else{ return null; } } // Called when a previously created loader has finished loading public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) List<PieEntry> entries = new ArrayList<>(); List<Integer> colors = new ArrayList<>(); int portion_idx = data.getColumnIndex(ActivityDiaryContract.DiaryStats.PORTION); int name_idx = data.getColumnIndex(ActivityDiaryContract.DiaryStats.NAME); int col_idx = data.getColumnIndex(ActivityDiaryContract.DiaryStats.COLOR); int dur_idx = data.getColumnIndex(ActivityDiaryContract.DiaryStats.DURATION); if ((data != null) && data.moveToFirst()) { float acc = 0.0f; float acc_po = 0.0f; while (!data.isAfterLast()) { float portion = data.getFloat(portion_idx); long duration = data.getLong(dur_idx); if(portion > 3.0f){ PieEntry ent = new PieEntry((float)duration, data.getString(name_idx)); entries.add(ent); colors.add(data.getInt(col_idx)); }else{ // accumulate the small, not shown entries acc += duration; acc_po += portion; } data.moveToNext(); } if(acc_po > 2.0f) { entries.add(new PieEntry(acc, getResources().getString(R.string.statistics_others))); colors.add(Color.GRAY); } } PieDataSet set = new PieDataSet(entries, getResources().getString(R.string.activities)); PieData dat = new PieData(set); set.setColors(colors); set.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { PieEntry e = (PieEntry)entry; return TimeSpanFormatter.format((long)e.getValue()); } }); chart.setData(dat); chart.setUsePercentValues(true); chart.setRotationAngle(180.0f); chart.invalidate(); // refresh } // Called when a previously created loader is reset, making the data unavailable public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. } @Override public void onResume() { mNavigationView.getMenu().findItem(R.id.nav_statistics).setChecked(true); super.onResume(); } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { currentDateTime = new Date().getTime(); Bundle bnd = new Bundle(); switch (position) { case 0: // all break; case 1: // last 7 days bnd.putLong("start", currentDateTime - (1000 * 60 * 60 * 24 * 7)); bnd.putLong("end", currentDateTime); break; case 2: // last 30 days bnd.putLong("start", currentDateTime - (1000 * 60 * 60 * 24 * 7 * 30)); bnd.putLong("end", currentDateTime); break; case 3: // Day currentOffset = 0; currentRange = Calendar.DAY_OF_YEAR; loadRange(currentRange, currentOffset); break; case 4: // week currentOffset = 0; currentRange = Calendar.WEEK_OF_YEAR; loadRange(currentRange, currentOffset); break; case 5: // month currentOffset = 0; currentRange = Calendar.MONTH; loadRange(currentRange, currentOffset); break; case 6: // year currentOffset = 0; currentRange = Calendar.YEAR; loadRange(currentRange, currentOffset); break; default: } if(position < 3){ rangeTextView.setVisibility(View.INVISIBLE); rangeEarlierImageView.setVisibility(View.INVISIBLE); rangeLaterImageView.setVisibility(View.INVISIBLE); }else{ rangeTextView.setVisibility(View.VISIBLE); rangeEarlierImageView.setVisibility(View.VISIBLE); rangeLaterImageView.setVisibility(View.VISIBLE); } if(position < 1) { getLoaderManager().restartLoader(LOADER_ID_TIME, bnd, this); }else if(position < 3){ getLoaderManager().restartLoader(LOADER_ID_RANGE, bnd, this); } } /* field is the field of Calender, e.g. Calendar.WEEK_OF_YEAR */ private void loadRange(int field, int offset){ Bundle bnd = new Bundle(); Calendar calStart = DateHelper.startOf(field, currentDateTime); calStart.add(field, offset); Calendar calEnd = (Calendar) calStart.clone(); calEnd.add(field, 1); SimpleDateFormat sdf = DateHelper.dateFormat(field); String tt = sdf.format(calStart.getTime()); rangeTextView.setText(tt); bnd.putLong("start", calStart.getTimeInMillis()); bnd.putLong("end", calEnd.getTimeInMillis()); getLoaderManager().restartLoader(LOADER_ID_RANGE, bnd, this); } @Override public void onNothingSelected(AdapterView<?> parent) { } public void showDatePickerDialog(View v) { HistoryDetailActivity.DatePickerFragment newFragment = new HistoryDetailActivity.DatePickerFragment(); Calendar date = Calendar.getInstance(); date.setTimeInMillis(currentDateTime); newFragment.setData(new DatePickerDialog.OnDateSetListener (){ @Override public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { date.set(Calendar.YEAR, year); date.set(Calendar.MONTH, month); date.set(Calendar.DAY_OF_MONTH, dayOfMonth); currentDateTime = date.getTimeInMillis(); currentOffset = 0; loadRange(currentRange, currentOffset); } } , date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DAY_OF_MONTH)); newFragment.show(getSupportFragmentManager(), "startDatePicker"); } }