davidmigloz/go-bees

View on GitHub
app/src/main/java/com/davidmiguel/gobees/recording/RecordingFragment.java

Summary

Maintainability
D
2 days
Test Coverage
/*
 * GoBees
 * Copyright (c) 2016 - 2017 David Miguel Lozano
 *
 * 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
 * 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/gpl-3.0.txt>.
 */

package com.davidmiguel.gobees.recording;

import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.davidmiguel.gobees.R;
import com.davidmiguel.gobees.data.model.MeteoRecord;
import com.davidmiguel.gobees.data.model.Record;
import com.davidmiguel.gobees.data.model.Recording;
import com.davidmiguel.gobees.data.source.preferences.GoBeesPreferences;
import com.davidmiguel.gobees.utils.AndroidUtils;
import com.davidmiguel.gobees.utils.HourAxisValueFormatter;
import com.davidmiguel.gobees.utils.RainValueFormatter;
import com.davidmiguel.gobees.utils.StringUtils;
import com.davidmiguel.gobees.utils.TempValueFormatter;
import com.davidmiguel.gobees.utils.WeatherUtils;
import com.davidmiguel.gobees.utils.WindValueFormatter;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Display recording detail.
 */
public class RecordingFragment extends Fragment implements RecordingContract.View {

    public static final String ARGUMENT_APIARY_ID = "APIARY_ID";
    public static final String ARGUMENT_HIVE_ID = "HIVE_ID";
    public static final String ARGUMENT_START_DATE = "START_DATE";
    public static final String ARGUMENT_END_DATE = "END_DATE";

    private RecordingContract.Presenter presenter;
    private LineChart beesChart;
    private LineChart tempChart;
    private LineChart rainChart;
    private LineChart windChart;
    private ImageView tempIcon;
    private ImageView rainIcon;
    private ImageView windIcon;

    private long referenceTimestamp;
    private long lastTimestamp;

    public RecordingFragment() {
        // Requires empty public constructor
    }

    public static RecordingFragment newInstance() {
        return new RecordingFragment();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.recording_frag, container, false);

        // Get charts
        beesChart = (LineChart) root.findViewById(R.id.recording_chart);
        tempChart = (LineChart) root.findViewById(R.id.temp_chart);
        rainChart = (LineChart) root.findViewById(R.id.rain_chart);
        windChart = (LineChart) root.findViewById(R.id.wind_chart);

        // Get icons
        tempIcon = (ImageView) root.findViewById(R.id.temp_icon);
        rainIcon = (ImageView) root.findViewById(R.id.rain_icon);
        windIcon = (ImageView) root.findViewById(R.id.wind_icon);

        // Set icons listeners
        tempIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                presenter.openTempChart();
            }
        });
        rainIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                presenter.openRainChart();
            }
        });
        windIcon.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                presenter.openWindChart();
            }
        });

        // Set up progress indicator
        LinearLayout ll = (LinearLayout) root.findViewById(R.id.linear_layout);
        AndroidUtils.setUpProgressIndicator(root, getContext(), ll, presenter);

        return root;
    }

    @Override
    public void onResume() {
        super.onResume();
        presenter.start();
    }

    @Override
    public void setLoadingIndicator(final boolean active) {
        AndroidUtils.setLoadingIndicator(getView(), active);
    }

    @Override
    public void showRecording(@NonNull Recording recording) {
        // Setup charts
        setupBeesChart(recording.getRecords());
        if (recording.getMeteo() != null && !recording.getMeteo().isEmpty()) {
            setupTempChart(recording.getMeteo());
            setupRainChart(recording.getMeteo());
            setupWindChart(recording.getMeteo());
        } else {
            showNoWeatherData();
        }
        // Show temp chart by default
        showTempChart();
    }

    @Override
    public void showLoadingRecordingError() {
        showMessage(getString(R.string.loading_recording_error));
    }

    @Override
    public void showNoRecords() {
        showMessage(getString(R.string.no_records_error));
    }

    @Override
    public void showTempChart() {
        setChartsVisibility("temp");
        highlightChartIcon("temp");
    }

    @Override
    public void showRainChart() {
        setChartsVisibility("rain");
        highlightChartIcon("rain");
    }

    @Override
    public void showWindChart() {
        setChartsVisibility("wind");
        highlightChartIcon("wind");
    }

    @Override
    public void showTitle(@NonNull Date date) {
        // Format date
        SimpleDateFormat formatter = new SimpleDateFormat(
                getString(R.string.recording_date_format), Locale.getDefault());
        String title = StringUtils.capitalize(formatter.format(date));
        // Set title
        ActionBar ab = ((RecordingActivity) getActivity()).getSupportActionBar();
        if (ab != null) {
            ab.setTitle(title);
        }
    }

    @Override
    public void setPresenter(@NonNull RecordingContract.Presenter presenter) {
        this.presenter = checkNotNull(presenter);
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }

    /**
     * Shows a snackbar with the given message.
     *
     * @param message message to show.
     */
    @SuppressWarnings("ConstantConditions")
    private void showMessage(String message) {
        Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show();
    }

    /**
     * Makes visible given chart and hides the others.
     *
     * @param chartVisible chart to make visible.
     */
    private void setChartsVisibility(String chartVisible) {
        tempChart.setVisibility("temp".equals(chartVisible) ? View.VISIBLE : View.GONE);
        rainChart.setVisibility("rain".equals(chartVisible) ? View.VISIBLE : View.GONE);
        windChart.setVisibility("wind".equals(chartVisible) ? View.VISIBLE : View.GONE);
    }

    /**
     * Highlightes given chart icon and softs the others.
     *
     * @param chartVisible chart to make icon visible.
     */
    private void highlightChartIcon(String chartVisible) {
        int defaultColor = ContextCompat.getColor(getContext(), R.color.colorDivider);
        tempIcon.setColorFilter("temp".equals(chartVisible)
                ? ContextCompat.getColor(getContext(), R.color.colorTempIcon) : defaultColor);
        rainIcon.setColorFilter("rain".equals(chartVisible)
                ? ContextCompat.getColor(getContext(), R.color.colorRainIcon) : defaultColor);
        windIcon.setColorFilter("wind".equals(chartVisible)
                ? ContextCompat.getColor(getContext(), R.color.colorWindIcon) : defaultColor);
    }

    /**
     * Configure bees chart and the data.
     *
     * @param recordsList list of records.
     */
    private void setupBeesChart(List<Record> recordsList) {
        // Setup data
        referenceTimestamp = recordsList.get(0).getTimestamp().getTime() / 1000;
        Record[] records = recordsList.toArray(new Record[recordsList.size()]);
        List<Entry> entries = new ArrayList<>();
        int maxNumBees = 0;
        for (Record record : records) {
            // Convert timestamp to seconds and relative to first timestamp
            long timestamp = record.getTimestamp().getTime() / 1000 - referenceTimestamp;
            int numBees = record.getNumBees();
            entries.add(new Entry(timestamp, numBees));
            // Get max num of bees
            if (numBees > maxNumBees) {
                maxNumBees = numBees;
            }
        }
        lastTimestamp = (long) entries.get(entries.size() - 1).getX();
        // Style char lines (type, color, etc.)
        LineDataSet lineDataSet = new LineDataSet(entries, getString(R.string.num_bees));
        lineDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
        lineDataSet.setCubicIntensity(0.2f);
        lineDataSet.setDrawValues(false);
        lineDataSet.setDrawCircles(false);
        lineDataSet.setLineWidth(1.8f);
        lineDataSet.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
        // General setup
        beesChart.setDrawGridBackground(false);
        beesChart.setDrawBorders(false);
        beesChart.setViewPortOffsets(80, 40, 80, 50);
        beesChart.getDescription().setEnabled(false);
        beesChart.getLegend().setEnabled(false);
        beesChart.setTouchEnabled(true);
        beesChart.setDragEnabled(false);
        beesChart.setScaleEnabled(false);
        beesChart.setPinchZoom(false);
        BeesMarkerView mv = new BeesMarkerView(getContext(), R.layout.recording_bees_marker_vew);
        mv.setChartView(beesChart);
        beesChart.setMarker(mv);
        beesChart.setNoDataText(getString(R.string.no_flight_act_data_available));
        // X axis setup
        IAxisValueFormatter xAxisFormatter = new HourAxisValueFormatter(referenceTimestamp);
        XAxis xAxis = beesChart.getXAxis();
        xAxis.setValueFormatter(xAxisFormatter);
        xAxis.setDrawGridLines(false);
        xAxis.setDrawAxisLine(false);
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
        xAxis.setTextColor(Color.BLACK);
        // Y axis setup
        YAxis leftAxis = beesChart.getAxisLeft();
        leftAxis.setAxisMaximum(maxNumBees > 40 ? maxNumBees + 2 : 40);
        leftAxis.setAxisMinimum(0);
        leftAxis.setDrawGridLines(true);
        leftAxis.setDrawAxisLine(false);
        YAxis rightAxis = beesChart.getAxisRight();
        rightAxis.setAxisMaximum(maxNumBees > 40 ? maxNumBees + 2 : 40);
        rightAxis.setAxisMinimum(0);
        rightAxis.setDrawGridLines(true);
        rightAxis.setDrawAxisLine(false);
        // Add data
        beesChart.setData(new LineData(lineDataSet));
    }

    /**
     * Configure temperature chart and the data.
     *
     * @param meteo meteo records.
     */
    private void setupTempChart(List<MeteoRecord> meteo) {
        // Setup data
        List<Entry> entries = new ArrayList<>();
        // Add as first entry a copy of the first temperature record
        // First relative timestamp is 0
        entries.add(new Entry(0, (float) meteo.get(0).getTemperature()));
        // Add all temperature records
        float maxTemp = Float.MIN_VALUE;
        float minTemp = Float.MAX_VALUE;
        for (MeteoRecord meteoRecord : meteo) {
            // Convert timestamp to seconds and relative to first timestamp
            long timestamp = meteoRecord.getTimestamp().getTime() / 1000 - referenceTimestamp;
            float temperature = (float) meteoRecord.getTemperature();
            entries.add(new Entry(timestamp, temperature));
            // Get max and min temperature
            if (temperature > maxTemp) {
                maxTemp = temperature;
            }
            if (temperature < minTemp) {
                minTemp = temperature;
            }
        }
        // Add as last entry a copy of the last temperature record
        entries.add(new Entry(lastTimestamp,
                (float) meteo.get(meteo.size() - 1).getTemperature()));
        // Style char lines (type, color, etc.)
        TempValueFormatter tempValueFormatter = new TempValueFormatter(
                GoBeesPreferences.isMetric(getContext()) ?
                        TempValueFormatter.Unit.CELSIUS : TempValueFormatter.Unit.FAHRENHEIT);
        tempChart.setData(new LineData(
                configureWeatherChart(tempChart, R.string.temperature,
                        R.color.colorLineTempChart, R.color.colorFillTempChart,
                        entries, tempValueFormatter, minTemp - 5, maxTemp + 5)
        ));
    }


    /**
     * Configure rain chart and the data.
     *
     * @param meteo meteo records.
     */
    private void setupRainChart(List<MeteoRecord> meteo) {
        // Setup data
        List<Entry> entries = new ArrayList<>();
        // Add as first entry a copy of the first rain record
        // First relative timestamp is 0
        entries.add(new Entry(0, (float) meteo.get(0).getRain()));
        // Add all rain records
        float maxRain = Float.MIN_VALUE;
        for (MeteoRecord meteoRecord : meteo) {
            // Convert timestamp to seconds and relative to first timestamp
            long timestamp = meteoRecord.getTimestamp().getTime() / 1000 - referenceTimestamp;
            float rain = (float) meteoRecord.getRain();
            entries.add(new Entry(timestamp, rain));
            // Get max and min temperature
            if (rain > maxRain) {
                maxRain = rain;
            }
        }
        // Add as last entry a copy of the last rain record
        entries.add(new Entry(lastTimestamp, (float) meteo.get(meteo.size() - 1).getRain()));
        // Style char lines (type, color, etc.)
        RainValueFormatter rainValueFormatter = new RainValueFormatter(RainValueFormatter.Unit.MM);
        rainChart.setData(new LineData(
                configureWeatherChart(rainChart, R.string.rain,
                        R.color.colorLineRainChart, R.color.colorFillRainChart,
                        entries, rainValueFormatter, 0, maxRain + 1)
        ));
    }


    /**
     * Configure wind chart and the data.
     *
     * @param meteo meteo records.
     */
    private void setupWindChart(List<MeteoRecord> meteo) {
        // Setup data
        List<Entry> entries = new ArrayList<>();
        // Add as first entry a copy of the first wind record
        // First relative timestamp is 0
        entries.add(new Entry(0, (float) meteo.get(0).getWindSpeed()));
        // Add all wind records
        float maxWind = Float.MIN_VALUE;
        for (MeteoRecord meteoRecord : meteo) {
            // Convert timestamp to seconds and relative to first timestamp
            long timestamp = meteoRecord.getTimestamp().getTime() / 1000 - referenceTimestamp;
            float wind = (float) meteoRecord.getWindSpeed();
            entries.add(new Entry(timestamp, wind));
            // Get max and min temperature
            if (wind > maxWind) {
                maxWind = wind;
            }
        }
        // Add as last entry a copy of the last wind record
        entries.add(new Entry(lastTimestamp, (float) meteo.get(meteo.size() - 1).getWindSpeed()));
        // Style char lines (type, color, etc.)
        WindValueFormatter windValueFormatter = new WindValueFormatter(WindValueFormatter.Unit.MS);
        windChart.setData(new LineData(
                configureWeatherChart(windChart, R.string.wind,
                        R.color.colorLineWindChart, R.color.colorFillWindChart,
                        entries, windValueFormatter, 0, maxWind + 5)
        ));
    }

    /**
     * Configure styles of weather charts.
     *
     * @param entries   chart data.
     * @param formatter value formatter.
     * @param minVal    min value to show.
     * @param maxVal    max value to show.
     * @return chart formatted.
     */
    private LineDataSet configureWeatherChart(
            LineChart chart, int chartName, int colorLineTempChart, int colorFillTempChart,
            List<Entry> entries, IAxisValueFormatter formatter, double minVal, double maxVal) {
        LineDataSet lineDataSet = new LineDataSet(entries, getString(chartName));
        lineDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
        lineDataSet.setDrawValues(false);
        lineDataSet.setValueTextSize(10f);
        lineDataSet.setDrawCircles(false);
        lineDataSet.setLineWidth(1.8f);
        lineDataSet.setColor(ContextCompat.getColor(getContext(), colorLineTempChart));
        lineDataSet.setLineWidth(2f);
        lineDataSet.setDrawFilled(true);
        lineDataSet.setFillColor(ContextCompat.getColor(getContext(), colorFillTempChart));
        lineDataSet.setFillAlpha(255);
        // General setup
        chart.setDrawGridBackground(false);
        chart.setDrawBorders(false);
        chart.setViewPortOffsets(0, 0, 0, 0);
        chart.getDescription().setEnabled(false);
        chart.getLegend().setEnabled(false);
        chart.setTouchEnabled(false);
        // X axis setup
        XAxis xAxis = chart.getXAxis();
        xAxis.setEnabled(false);
        xAxis.setAxisMinimum(0);
        xAxis.setAxisMaximum(lastTimestamp);
        // Y axis setup
        YAxis leftAxis = chart.getAxisLeft();
        leftAxis.setEnabled(false);
        leftAxis.setAxisMaximum((float) (maxVal));
        leftAxis.setAxisMinimum((float) (minVal));
        YAxis rightAxis = chart.getAxisRight();
        rightAxis.setAxisMaximum((float) (maxVal));
        rightAxis.setAxisMinimum((float) (minVal));
        rightAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
        rightAxis.setValueFormatter(formatter);
        return lineDataSet;
    }

    /**
     * Shows no weather data message.
     */
    private void showNoWeatherData() {
        float textSize = WeatherUtils.convertDpToPixel(getResources(), 12);
        tempChart.setNoDataText(getString(R.string.no_weather_data_available));
        tempChart.getPaint(Chart.PAINT_INFO).setTextSize(textSize);
        rainChart.setNoDataText(getString(R.string.no_weather_data_available));
        rainChart.getPaint(Chart.PAINT_INFO).setTextSize(textSize);
        windChart.setNoDataText(getString(R.string.no_weather_data_available));
        windChart.getPaint(Chart.PAINT_INFO).setTextSize(textSize);
    }
}