Java tutorial
/* * Copyright (C) 2017 Florian Dreier * * This file is part of MyTargets. * * MyTargets is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * MyTargets 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. */ package de.dreier.mytargets.features.statistics; import android.app.LoaderManager; import android.content.AsyncTaskLoader; import android.content.Intent; import android.content.Loader; import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.util.LongSparseArray; import android.support.v4.util.Pair; import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.afollestad.materialdialogs.MaterialDialog; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import java.io.IOException; import java.util.Collections; import java.util.List; import de.dreier.mytargets.R; import de.dreier.mytargets.base.activities.ChildActivityBase; import de.dreier.mytargets.databinding.ActivityStatisticsBinding; import de.dreier.mytargets.shared.models.Target; import de.dreier.mytargets.shared.models.db.Arrow; import de.dreier.mytargets.shared.models.db.Bow; import de.dreier.mytargets.shared.models.db.Round; import de.dreier.mytargets.shared.models.db.Training; import de.dreier.mytargets.shared.utils.LongUtils; import de.dreier.mytargets.shared.utils.ParcelsBundler; import de.dreier.mytargets.shared.utils.SharedUtils; import de.dreier.mytargets.utils.IntentWrapper; import de.dreier.mytargets.utils.ToolbarUtils; import icepick.Icepick; import icepick.State; public class StatisticsActivity extends ChildActivityBase implements LoaderManager.LoaderCallbacks<List<Pair<Training, Round>>> { @VisibleForTesting public static final String ROUND_IDS = "round_ids"; @State boolean showFilter = false; private ActivityStatisticsBinding binding; private List<Pair<Training, Round>> rounds; private List<Pair<Target, List<Round>>> filteredRounds; @State(ParcelsBundler.class) List<String> distanceTags; @State(ParcelsBundler.class) List<String> diameterTags; @State(ParcelsBundler.class) List<Long> arrowTags; @State(ParcelsBundler.class) List<Long> bowTags; @NonNull public static IntentWrapper getIntent(List<Long> roundIds) { return new IntentWrapper(StatisticsActivity.class).with(ROUND_IDS, LongUtils.toArray(roundIds)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_statistics); setSupportActionBar(binding.toolbar); binding.progressBar.show(); ToolbarUtils.showHomeAsUp(this); Icepick.restoreInstanceState(this, savedInstanceState); getLoaderManager().initLoader(0, getIntent().getExtras(), this).forceLoad(); } @Override public Loader<List<Pair<Training, Round>>> onCreateLoader(int i, Bundle bundle) { final long[] roundIds = getIntent().getLongArrayExtra(ROUND_IDS); return new AsyncTaskLoader<List<Pair<Training, Round>>>(this) { @Override public List<Pair<Training, Round>> loadInBackground() { final List<Round> rounds = Round.getAll(roundIds); LongSparseArray<Training> trainingsMap = new LongSparseArray<>(); Stream.of(rounds).map(round -> round.trainingId).distinct().map(Training::get) .forEach(training -> trainingsMap.append(training.getId(), training)); return Stream.of(rounds).map(round -> new Pair<>(trainingsMap.get(round.trainingId), round)) .collect(Collectors.toList()); } }; } @Override public void onLoadFinished(Loader<List<Pair<Training, Round>>> loader, List<Pair<Training, Round>> data) { rounds = data; binding.progressBar.hide(); binding.distanceTags.setTags(getDistanceTags()); binding.distanceTags.setOnTagClickListener(t -> applyFilter()); binding.diameterTags.setTags(getDiameterTags()); binding.diameterTags.setOnTagClickListener(t -> applyFilter()); binding.arrowTags.setTags(getArrowTags()); binding.arrowTags.setOnTagClickListener(t -> applyFilter()); binding.bowTags.setTags(getBowTags()); binding.bowTags.setOnTagClickListener(t -> applyFilter()); if (distanceTags != null && diameterTags != null && arrowTags != null && bowTags != null) { restoreCheckedStates(); } updateFilter(); } private void restoreCheckedStates() { Stream.of(binding.distanceTags.getTags()).forEach( tag -> tag.isChecked = Stream.of(distanceTags).anyMatch(d -> SharedUtils.equals(d, tag.text))); Stream.of(binding.diameterTags.getTags()).forEach( tag -> tag.isChecked = Stream.of(diameterTags).anyMatch(d -> SharedUtils.equals(d, tag.text))); Stream.of(binding.arrowTags.getTags()) .forEach(tag -> tag.isChecked = Stream.of(arrowTags).anyMatch(a -> SharedUtils.equals(a, tag.id))); Stream.of(binding.bowTags.getTags()) .forEach(tag -> tag.isChecked = Stream.of(bowTags).anyMatch(b -> SharedUtils.equals(b, tag.id))); binding.distanceTags.setTags(binding.distanceTags.getTags()); binding.diameterTags.setTags(binding.diameterTags.getTags()); binding.arrowTags.setTags(binding.arrowTags.getTags()); binding.bowTags.setTags(binding.bowTags.getTags()); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.filter, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); final MenuItem filter = menu.findItem(R.id.action_filter); final MenuItem export = menu.findItem(R.id.action_export); filter.setIcon(showFilter ? R.drawable.ic_clear_filter_white_24dp : R.drawable.ic_filter_white_24dp); // only show filter if we have at least one category to filter by boolean filterAvailable = binding.distanceTags.getTags().size() > 1 || binding.diameterTags.getTags().size() > 1 || binding.bowTags.getTags().size() > 1 || binding.arrowTags.getTags().size() > 1; filter.setVisible(rounds != null && filterAvailable); export.setVisible(rounds != null); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_export: export(); return true; case R.id.action_filter: showFilter = !showFilter; updateFilter(); return true; default: return super.onOptionsItemSelected(item); } } protected void updateFilter() { if (!showFilter) { resetFilter(); } binding.filterView.setVisibility(showFilter ? View.VISIBLE : View.GONE); applyFilter(); invalidateOptionsMenu(); } private void resetFilter() { Stream.of(binding.distanceTags.getTags()).forEach(tag -> tag.isChecked = true); Stream.of(binding.diameterTags.getTags()).forEach(tag -> tag.isChecked = true); Stream.of(binding.arrowTags.getTags()).forEach(tag -> tag.isChecked = true); Stream.of(binding.bowTags.getTags()).forEach(tag -> tag.isChecked = true); binding.distanceTags.setTags(binding.distanceTags.getTags()); binding.diameterTags.setTags(binding.diameterTags.getTags()); binding.arrowTags.setTags(binding.arrowTags.getTags()); binding.bowTags.setTags(binding.bowTags.getTags()); } private void applyFilter() { distanceTags = Stream.of(binding.distanceTags.getCheckedTags()).map(t -> t.text) .collect(Collectors.toList()); diameterTags = Stream.of(binding.diameterTags.getCheckedTags()).map(t -> t.text) .collect(Collectors.toList()); arrowTags = Stream.of(binding.arrowTags.getCheckedTags()).map(t -> t.id).collect(Collectors.toList()); bowTags = Stream.of(binding.bowTags.getCheckedTags()).map(t -> t.id).collect(Collectors.toList()); filteredRounds = Stream.of(rounds) .filter(pair -> distanceTags.contains(pair.second.distance.toString()) && diameterTags.contains(pair.second.getTarget().size.toString()) && arrowTags.contains(pair.first.arrowId) && bowTags.contains(pair.first.bowId)) .map(p -> p.second) .groupBy(value -> new Pair<>(value.getTarget().getId(), value.getTarget().scoringStyle)) .map(value1 -> new Pair<>(value1.getValue().get(0).getTarget(), value1.getValue())) .collect(Collectors.toList()); boolean animate = binding.viewPager.getAdapter() == null; final StatisticsPagerAdapter adapter = new StatisticsPagerAdapter(getSupportFragmentManager(), filteredRounds, animate); binding.viewPager.setAdapter(adapter); } private List<ChipGroup.Tag> getBowTags() { return Stream.of(rounds).map(p -> p.first.bowId).distinct().map(bid -> { if (bid != null) { Bow bow = Bow.get(bid); if (bow == null) { return new ChipGroup.Tag(bid, "Deleted " + bid); } return new ChipGroup.Tag(bow.getId(), bow.getName(), bow.thumbnail.getBlob().getBlob(), true); } else { return new ChipGroup.Tag(null, getString(R.string.unknown)); } }).collect(Collectors.toList()); } private List<ChipGroup.Tag> getArrowTags() { return Stream.of(rounds).map(p -> p.first.arrowId).distinct().map(aid -> { if (aid != null) { Arrow arrow = Arrow.get(aid); if (arrow == null) { return new ChipGroup.Tag(aid, "Deleted " + aid); } return new ChipGroup.Tag(arrow.getId(), arrow.getName(), arrow.thumbnail.getBlob().getBlob(), true); } else { return new ChipGroup.Tag(null, getString(R.string.unknown)); } }).collect(Collectors.toList()); } private List<ChipGroup.Tag> getDistanceTags() { return Stream.of(rounds).map(p -> p.second.distance).distinct().sorted() .map(d -> new ChipGroup.Tag(d.getId(), d.toString())).collect(Collectors.toList()); } private List<ChipGroup.Tag> getDiameterTags() { return Stream.of(rounds).map(p -> p.second.getTarget().size).distinct().sorted() .map(d -> new ChipGroup.Tag(d.getId(), d.toString())).collect(Collectors.toList()); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @Override public void onLoaderReset(Loader<List<Pair<Training, Round>>> loader) { } void export() { MaterialDialog progress = new MaterialDialog.Builder(this).content(R.string.exporting).progress(true, 0) .show(); new AsyncTask<Void, Void, Uri>() { @Override protected Uri doInBackground(Void... params) { try { return CsvExporter.export(getApplicationContext(), Stream.of(filteredRounds) .flatMap(p -> Stream.of(p.second)).map(Round::getId).collect(Collectors.toList())); } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(Uri uri) { super.onPostExecute(uri); progress.dismiss(); if (uri != null) { Intent email = new Intent(Intent.ACTION_SEND); email.putExtra(Intent.EXTRA_STREAM, uri); email.setType("text/csv"); startActivity(Intent.createChooser(email, getString(R.string.send_exported))); } else { Snackbar.make(binding.getRoot(), R.string.exporting_failed, Snackbar.LENGTH_LONG).show(); } } }.execute(); } private class StatisticsPagerAdapter extends FragmentStatePagerAdapter { private final List<Pair<Target, List<Round>>> targets; private final boolean animate; StatisticsPagerAdapter(FragmentManager fm, List<Pair<Target, List<Round>>> pairs, boolean animate) { super(fm); targets = pairs; this.animate = animate; Collections.sort(targets, (p1, p2) -> p2.second.size() - p1.second.size()); } @Override public Fragment getItem(int position) { final Pair<Target, List<Round>> item = targets.get(position); final List<Long> roundIds = Stream.of(item.second).map(Round::getId).collect(Collectors.toList()); return StatisticsFragment.newInstance(roundIds, item.first, animate); } @Override public int getCount() { return targets.size(); } @Override public CharSequence getPageTitle(int position) { return targets.get(position).first.toString(); } } }