app/src/main/java/com/davidmiguel/gobees/recording/RecordingFragment.java
/*
* 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);
}
}