/*
* Copyright (C) 2013 jonas.oreland@gmail.com
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.runnerup.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.format.DateUtils;
import android.util.Log;
import org.runnerup.R;
import org.runnerup.workout.Dimension;
import java.text.DecimalFormat;
import java.util.Locale;
@TargetApi(Build.VERSION_CODES.FROYO)
public class Formatter implements OnSharedPreferenceChangeListener {
private Context context = null;
private Resources resources = null;
private LocaleResources cueResources = null;
private SharedPreferences sharedPreferences = null;
private java.text.DateFormat dateFormat = null;
private java.text.DateFormat timeFormat = null;
//private HRZones hrZones = null;
private final Locale defaultLocale;
private boolean km = true;
private String base_unit = "km";
private double base_meters = km_meters;
public final static double km_meters = 1000.0;
public final static double mi_meters = 1609.34;
//public final static double FEET_PER_METER = 3.2808;
public enum Format {
CUE, // for text to speech
CUE_SHORT, // brief for tts
CUE_LONG, // long for tts
TXT, // same as TXT_SHORT but without unit
TXT_SHORT, // brief for printing
TXT_LONG, // long for printing
TXT_TIMESTAMP, // For current time e.g 13:41:24
}
public Formatter(Context ctx) {
context = ctx;
resources = ctx.getResources();
cueResources = getCueLangResources(ctx);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx);
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
dateFormat = android.text.format.DateFormat.getDateFormat(ctx);
timeFormat = android.text.format.DateFormat.getTimeFormat(ctx);
//hrZones = new HRZones(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
!ctx.getResources().getConfiguration().getLocales().isEmpty()) {
defaultLocale = ctx.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
defaultLocale = ctx.getResources().getConfiguration().locale;
}
setUnit();
}
private class LocaleResources {
final Resources resources;
final Configuration configuration;
final Locale defaultLocale;
final Locale audioLocale;
LocaleResources(Context ctx, Locale locale) {
resources = ctx.getResources();
configuration = resources.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
!ctx.getResources().getConfiguration().getLocales().isEmpty()) {
defaultLocale = configuration.getLocales().get(0);
} else {
//noinspection deprecation
defaultLocale = configuration.locale;
}
audioLocale = locale;
}
void setLang(Locale locale) {
if (Build.VERSION.SDK_INT >= 17) {
configuration.setLocale(locale);
} else {
//noinspection deprecation
configuration.locale = locale;
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}
public String getString(int id) throws Resources.NotFoundException {
setLang(audioLocale);
String result = resources.getString(id);
setLang(defaultLocale);
return result;
}
public String getQuantityString(int id, int quantity, Object ... formatArgs) throws Resources.NotFoundException {
setLang(audioLocale);
String result = resources.getQuantityString(id, quantity, formatArgs);
setLang(defaultLocale);
return result;
}
}
public static Locale getAudioLocale(Context ctx) {
Resources res = ctx.getResources();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
if (prefs.contains(res.getString(R.string.pref_audio_lang))) {
Log.e("Formatter", "Audio language: " +
prefs.getString(res.getString(R.string.pref_audio_lang), null));
return new Locale(prefs.getString(res.getString(R.string.pref_audio_lang), "en"));
}
return null;
}
private LocaleResources getCueLangResources(Context ctx) {
Locale loc = getAudioLocale(ctx);
if (loc == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
!ctx.getResources().getConfiguration().getLocales().isEmpty()) {
loc = ctx.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
loc = ctx.getResources().getConfiguration().locale;
}
}
return new LocaleResources(ctx, loc);
}
public String getCueString(int msgId) {
return cueResources.getString(msgId);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key != null && context.getString(R.string.pref_unit).contentEquals(key))
setUnit();
}
private void setUnit() {
km = getUseKilometers(context.getResources(), sharedPreferences, null);
if (km) {
base_unit = "km";
base_meters = km_meters;
} else {
base_unit = "mi";
base_meters = mi_meters;
}
}
public String getDistanceUnit(Format target) {
switch (target) {
case CUE:
case CUE_LONG:
case CUE_SHORT:
case TXT_LONG:
//No string for long - not used
// return resources.getString(km ? R.plurals.cue_kilometer : R.plurals.cue_mile);
case TXT:
case TXT_SHORT:
return resources.getString(km ? R.string.metrics_distance_km : R.string.metrics_distance_mi);
}
return null;
}
public static boolean getUseKilometers(Resources res, SharedPreferences prefs, Editor editor) {
boolean _km;
String unit = prefs.getString(res.getString(R.string.pref_unit), null);
if (unit == null)
_km = guessDefaultUnit(res, prefs, editor);
else if (unit.contentEquals("km"))
_km = true;
else if (unit.contentEquals("mi"))
_km = false;
else
_km = guessDefaultUnit(res, prefs, editor);
return _km;
}
private static boolean guessDefaultUnit(Resources res, SharedPreferences prefs, Editor editor) {
String countryCode = Locale.getDefault().getCountry();
Log.e("Formatter", "guessDefaultUnit: countryCode: " + countryCode);
if (countryCode == null)
return true; // km;
String key = res.getString(R.string.pref_unit);
if ("US".contentEquals(countryCode) ||
"GB".contentEquals(countryCode)) {
if (editor != null)
editor.putString(key, "mi");
return false;
}
else {
if (editor != null)
editor.putString(key, "km");
}
return true;
}
public double getUnitMeters() {
return this.base_meters;
}
public static double getUnitMeters(Resources res, SharedPreferences prefs) {
if (getUseKilometers(res, prefs, null))
return km_meters;
else
return mi_meters;
}
public String getUnitString() {
return this.base_unit;
}
public String format(Format target, Dimension dimension, double value) {
switch (dimension) {
case DISTANCE:
return formatDistance(target, Math.round(value));
case TIME:
return formatElapsedTime(target, Math.round(value));
case PACE:
return formatPace(target, value);
case HR:
return formatHeartRate(target, value);
case HRZ:
return formatHeartRateZone(target, value);
case SPEED:
return formatSpeed(target, value);
case CAD:
return formatCadence(target, value);
case TEMPERATURE:
return formatCadence(target, value);//TODO
case PRESSURE:
return formatCadence(target, value);//TODO
}
return "";
}
public String formatElapsedTime(Format target, long seconds) {
switch (target) {
case CUE:
case CUE_SHORT:
return cueElapsedTime(seconds, false);
case CUE_LONG:
return cueElapsedTime(seconds, true);
case TXT:
case TXT_SHORT:
return DateUtils.formatElapsedTime(seconds);
case TXT_LONG:
return txtElapsedTime(seconds);
case TXT_TIMESTAMP:
return formatTime(seconds);
}
return "";
}
private String cueElapsedTime(long seconds, boolean includeDimension) {
long hours = 0;
long minutes = 0;
if (seconds >= 3600) {
hours = seconds / 3600;
seconds -= hours * 3600;
}
if (seconds >= 60) {
minutes = seconds / 60;
seconds -= minutes * 60;
}
StringBuilder s = new StringBuilder();
if (hours > 0) {
includeDimension = true;
s.append(cueResources.getQuantityString(R.plurals.cue_hour, (int)hours, (int)hours));
}
if (minutes > 0) {
if (hours > 0)
s.append(" ");
includeDimension = true;
s.append(cueResources.getQuantityString(R.plurals.cue_minute, (int)minutes, (int)minutes));
}
if (seconds > 0) {
if (hours > 0 || minutes > 0)
s.append(" ");
if (includeDimension) {
s.append(cueResources.getQuantityString(R.plurals.cue_second, (int)seconds, (int)seconds));
} else {
s.append(seconds);
}
}
return s.toString();
}
private String txtElapsedTime(long seconds) {
long hours = 0;
long minutes = 0;
if (seconds >= 3600) {
hours = seconds / 3600;
seconds -= hours * 3600;
}
if (seconds >= 60) {
minutes = seconds / 60;
seconds -= minutes * 60;
}
StringBuilder s = new StringBuilder();
if (hours > 0) {
s.append(hours).append(" ").append(resources.getString(R.string.metrics_elapsed_h));
}
if (minutes > 0) {
if (hours > 0)
s.append(" ");
if (hours > 0 || seconds > 0)
s.append(minutes).append(" ").append(resources.getString(R.string.metrics_elapsed_m));
else
s.append(minutes).append(" ").append(resources.getString(R.string.metrics_elapsed_min));
}
if (seconds > 0) {
if (hours > 0 || minutes > 0)
s.append(" ");
s.append(seconds).append(" ").append(resources.getString(R.string.metrics_elapsed_s));
}
return s.toString();
}
/**
* Format heart rate
*
* @param target
* @param heart_rate
* @return
*/
public String formatHeartRate(Format target, double heart_rate) {
int bpm = (int) Math.round(heart_rate);
switch (target) {
case CUE:
case CUE_SHORT:
case CUE_LONG:
return cueResources.getQuantityString(R.plurals.cue_bpm, bpm, bpm);
case TXT:
case TXT_SHORT:
case TXT_LONG:
return Integer.toString(bpm);
}
return "";
}
/**
* Format cadence
*
* @param target
* @param val
* @return
*/
public String formatCadence(Format target, double val) {
switch (target) {
case CUE:
case CUE_SHORT:
case CUE_LONG:
return Integer.toString((int) Math.round(val)) + " "
+ cueResources.getQuantityString(R.plurals.cue_bpm, (int)val);
case TXT:
case TXT_SHORT:
case TXT_LONG:
return Integer.toString((int) Math.round(val));
}
return "";
}
private String formatHeartRateZone(Format target, double hrZone) {
switch (target) {
case TXT:
case TXT_SHORT:
return Integer.toString((int) Math.round(hrZone));
case TXT_LONG:
return Double.toString(Math.round(10.0 * hrZone) / 10.0);
case CUE_SHORT:
return cueResources.getString(R.string.heart_rate_zone) + " "
+ Integer.toString((int) Math.floor(hrZone));
case CUE:
case CUE_LONG:
return cueResources.getString(R.string.heart_rate_zone) + " "
+ Double.toString(Math.floor(10.0 * hrZone) / 10.0);
}
return "";
}
/**
* Format pace
*
* @param target
* @param seconds_per_meter
* @return
*/
public String formatPace(Format target, double seconds_per_meter) {
switch (target) {
case CUE:
case CUE_SHORT:
case CUE_LONG:
return cuePace(seconds_per_meter);
case TXT:
case TXT_SHORT:
return txtPace(seconds_per_meter, false);
case TXT_LONG:
return txtPace(seconds_per_meter, true);
}
return "";
}
/**
* @return pace unit string
*/
public String getPaceUnit() {//Resources resources, SharedPreferences sharedPreferences) {
int du = km ? R.string.metrics_distance_km : R.string.metrics_distance_mi;
return resources.getString(R.string.metrics_elapsed_min) + "/" + resources.getString(du);
}
/**
* @param seconds_per_meter
* @return string suitable for printing according to settings
*/
private String txtPace(double seconds_per_meter, boolean includeUnit) {
long val = Math.round(base_meters * seconds_per_meter);
String str = DateUtils.formatElapsedTime(val);
if (!includeUnit)
return str;
else {
int res = km ? R.string.metrics_distance_km : R.string.metrics_distance_mi;
return str + "/" + resources.getString(res);
}
}
private String cuePace(double seconds_per_meter) {
long seconds_per_unit = Math.round(base_meters * seconds_per_meter);
long hours_per_unit = 0;
long minutes_per_unit = 0;
if (seconds_per_unit >= 3600) {
hours_per_unit = seconds_per_unit / 3600;
seconds_per_unit -= hours_per_unit * 3600;
}
if (seconds_per_unit >= 60) {
minutes_per_unit = seconds_per_unit / 60;
seconds_per_unit -= minutes_per_unit * 60;
}
StringBuilder s = new StringBuilder();
if (hours_per_unit > 0) {
s.append(cueResources.getQuantityString(R.plurals.cue_hour, (int)hours_per_unit, (int)hours_per_unit));
}
if (minutes_per_unit > 0) {
if (hours_per_unit > 0)
s.append(" ");
s.append(cueResources.getQuantityString(R.plurals.cue_minute, (int)minutes_per_unit, (int)minutes_per_unit));
}
if (seconds_per_unit > 0) {
if (hours_per_unit > 0 || minutes_per_unit > 0)
s.append(" ");
s.append(cueResources.getQuantityString(R.plurals.cue_second, (int)seconds_per_unit, (int)seconds_per_unit));
}
s.append(" ").append(cueResources.getString(km ? R.string.cue_perkilometer : R.string.cue_permile));
return s.toString();
}
/**
* Format Speed
*
* @param target
* @param seconds_per_meter
* @return
*/
public String formatSpeed(Format target, double seconds_per_meter) {
switch (target) {
case CUE:
case CUE_SHORT:
case CUE_LONG:
return cueSpeed(seconds_per_meter);
case TXT:
case TXT_SHORT:
return txtSpeed(seconds_per_meter, false);
case TXT_LONG:
return txtSpeed(seconds_per_meter, true);
}
return "";
}
/**
* @param meter_per_seconds
* @return string suitable for printing according to settings
*/
private String txtSpeed(double meter_per_seconds, boolean includeUnit) {
double distance_per_seconds = meter_per_seconds / base_meters;
double distance_per_hour = distance_per_seconds * 3600;
String str = String.format(defaultLocale, "%.1f", distance_per_hour);
if (!includeUnit)
return str;
else {
int res = km ? R.string.metrics_distance_km : R.string.metrics_distance_mi;
return str +
resources.getString(res) +
"/" +
resources.getString(R.string.metrics_elapsed_h);
}
}
private String cueSpeed(double meter_per_seconds) {
double distance_per_seconds = meter_per_seconds / base_meters;
double distance_per_hour = distance_per_seconds * 3600;
String fmtDistPerHour = txtSpeed(meter_per_seconds, false);
return cueResources.getQuantityString(km ? R.plurals.cue_kilometers_per_hour : R.plurals.cue_miles_per_hour,
(int)distance_per_hour, fmtDistPerHour);
}
/**
* @param seconds_since_epoch
* @return
*/
public String formatDateTime(long seconds_since_epoch) {
// ignore target
// milliseconds
// as argument
return dateFormat.format(seconds_since_epoch * 1000) +
" " +
timeFormat.format(seconds_since_epoch * 1000);
}
/**
* @param target
* @param meters
* @return
*/
public String formatDistance(Format target, long meters) {
switch (target) {
case CUE:
case CUE_LONG:
case CUE_SHORT:
return cueDistance(meters, false);
case TXT:
return cueDistanceInKmOrMiles(meters);
case TXT_SHORT:
return cueDistance(meters, true);
case TXT_LONG:
return Long.toString(meters) + " m";
}
return null;
}
private String cueDistanceInKmOrMiles(long meters) {
DecimalFormat df = new DecimalFormat("#.00");
return df.format(meters / base_meters);
}
private String cueDistance(long meters, boolean txt) {
double base_val = km_meters; // 1km
double decimals = 2;
if (!km) {
base_val = mi_meters;
}
StringBuilder s = new StringBuilder();
if (meters >= base_val) {
double base = ((double) meters) / base_val;
double val = round(base, decimals);
if (txt) {
s.append(val).append(" ")
.append(resources.getString(km ? R.string.metrics_distance_km : R.string.metrics_distance_mi));
} else {
s.append(val).append(" ")
.append(cueResources.getQuantityString(km ? R.plurals.cue_kilometer : R.plurals.cue_mile, (int)val));
}
} else {
if (txt)
s.append(meters).append(" ").append("m");
else
s.append(cueResources.getQuantityString(R.plurals.cue_meter, (int)meters, (int)meters));
}
return s.toString();
}
public String formatRemaining(Format target, Dimension dimension, double value) {
switch (dimension) {
case DISTANCE:
return formatRemainingDistance(target, value);
case TIME:
return formatRemainingTime(target, value);
case PACE:
case SPEED:
case HR:
case CAD:
case TEMPERATURE:
case PRESSURE:
default:
break;
}
return "";
}
public String formatRemainingTime(Format target, double value) {
return formatElapsedTime(target, Math.round(value));
}
public String formatRemainingDistance(Format target, double value) {
return formatDistance(target, Math.round(value));
}
public String formatName(String first, String last) {
if (first != null && last != null)
return first + " " + last;
else if (first == null && last != null)
return last;
else if (first != null && last == null)
return first;
return "";
}
public String formatTime(long seconds_since_epoch) {
return timeFormat.format(seconds_since_epoch * 1000);
}
public static double round(double base, double decimals) {
double exp = Math.pow(10, decimals);
return Math.round(base * exp) / exp;
}
public static double getUnitMeters(Context mContext) {
return getUnitMeters(mContext.getResources(),
PreferenceManager.getDefaultSharedPreferences(mContext));
}
}