diff --git a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java index 8a897493..56107b9b 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java +++ b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java @@ -27,6 +27,8 @@ import android.widget.Toast; import com.health.openscale.R; import com.health.openscale.core.alarm.AlarmHandler; import com.health.openscale.core.bluetooth.BluetoothCommunication; +import com.health.openscale.core.bodymetric.EstimatedFatMetric; +import com.health.openscale.core.bodymetric.EstimatedWaterMetric; import com.health.openscale.core.database.ScaleDatabase; import com.health.openscale.core.database.ScaleUserDatabase; import com.health.openscale.core.datatypes.ScaleData; @@ -93,7 +95,7 @@ public class OpenScale { scaleUser.body_height = body_height; scaleUser.scale_unit = scale_unit; scaleUser.gender = gender; - scaleUser.initial_weight = initial_weight; + scaleUser.setConvertedInitialWeight(initial_weight); scaleUser.goal_weight = goal_weight; scaleUser.goal_date = new SimpleDateFormat("dd.MM.yyyy").parse(goal_date); @@ -150,7 +152,7 @@ public class OpenScale { scaleUser.body_height = body_height; scaleUser.scale_unit = scale_unit; scaleUser.gender = gender; - scaleUser.initial_weight = initial_weight; + scaleUser.setConvertedInitialWeight(initial_weight); scaleUser.goal_weight = goal_weight; scaleUser.goal_date = new SimpleDateFormat("dd.MM.yyyy").parse(goal_date); } catch (ParseException e) { @@ -188,6 +190,18 @@ public class OpenScale { } } + if (prefs.getBoolean("estimateFatEnable", false)) { + EstimatedFatMetric fatMetric = EstimatedFatMetric.getEstimatedFatMetric(EstimatedFatMetric.FORMULA_FAT.valueOf(prefs.getString("estimateFatFormula", "BF_DEURENBERG_II"))); + + scaleData.setFat(fatMetric.getFat(getScaleUser(scaleData.getUserId()), scaleData)); + } + + if (prefs.getBoolean("estimateWaterEnable", false)) { + EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedWaterMetric(EstimatedWaterMetric.FORMULA_WATER.valueOf(prefs.getString("estimateWaterFormula", "TBW_BEHNKE"))); + + scaleData.setWater(waterMetric.getWater(getScaleUser(scaleData.getUserId()), scaleData)); + } + if (scaleDB.insertEntry(scaleData)) { ScaleUser scaleUser = getScaleUser(scaleData.getUserId()); @@ -212,7 +226,7 @@ public class OpenScale { if (scaleUserData.size() > 0) { lastWeight = scaleUserData.get(0).getWeight(); } else { - lastWeight = scaleUser.get(i).initial_weight; + lastWeight = scaleUser.get(i).getInitialWeight(); } if ((lastWeight - range) <= weight && (lastWeight + range) >= weight) { diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java index d72a7201..5d6b217a 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothCommunication.java @@ -38,7 +38,7 @@ public abstract class BluetoothCommunication { BT_CONNECTION_LOST, BT_NO_DEVICE_FOUND, BT_UNEXPECTED_ERROR, BT_SCALE_MESSAGE }; public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE} - public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI,MGB} + public enum BT_DEVICE_ID {CUSTOM_OPENSCALE, MI_SCALE_V1, MI_SCALE_V2, SANITAS_SBF70, MEDISANA_BS444, DIGOO_DGS038H, EXCELVANT_CF369BLE, YUNMAI_MINI, YUNMAI_SE, MGB} protected Context context; @@ -81,6 +81,8 @@ public abstract class BluetoothCommunication { return new BluetoothCustomOpenScale(context); case MI_SCALE_V1: return new BluetoothMiScale(context); + case MI_SCALE_V2: + return new BluetoothMiScale2(context); case SANITAS_SBF70: return new BluetoothSanitasSbf70(context); case MEDISANA_BS444: @@ -91,8 +93,10 @@ public abstract class BluetoothCommunication { return new BluetoothExcelvanCF369BLE(context); case YUNMAI_MINI: return new BluetoothYunmaiMini(context); - case MGB: - return new BluetoothMGB(context); + case YUNMAI_SE: + return new BluetoothYunmaiSE(context); + case MGB: + return new BluetoothMGB(context); } return null; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java new file mode 100644 index 00000000..8590e267 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothMiScale2.java @@ -0,0 +1,262 @@ +/* Copyright (C) 2014 olie.xdev +* +* 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 +*/ + +package com.health.openscale.core.bluetooth; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.health.openscale.core.OpenScale; +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Random; +import java.util.UUID; + +import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS_CODE.BT_UNEXPECTED_ERROR; + +public class BluetoothMiScale2 extends BluetoothCommunication { + private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000181b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700"); + private final UUID WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_BODY_COMPOSITION_FEATURE = UUID.fromString("00002a9b-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_BODY_COMPOSITION_MEASUREMENT = UUID.fromString("00002a9c-0000-1000-8000-00805f9b34fb"); + + private final UUID WEIGHT_CUSTOM_SERVICE = UUID.fromString("00001530-0000-3512-2118-0009af100700"); + private final UUID WEIGHT_CUSTOM_CONFIG = UUID.fromString("00001542-0000-3512-2118-0009af100700"); + + public BluetoothMiScale2(Context context) { + super(context); + } + + @Override + public String deviceName() { + return "Xiaomi Mi Scale v2"; + } + + @Override + public String defaultDeviceName() { + return "MIBCS"; + } + + @Override + public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) { + final byte[] data = gattCharacteristic.getValue(); + + if (data != null && data.length > 0) { + Log.d("MIScale_v2", "DataChange hex data: "+ byteInHex(data)); + + // Stop command from mi scale received + if (data[0] == 0x03) { + setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE); + } + + if (data.length == 26) { + final byte[] firstWeight = Arrays.copyOfRange(data, 0, 10); + final byte[] secondWeight = Arrays.copyOfRange(data, 10, 20); + parseBytes(firstWeight); + parseBytes(secondWeight); + } + + if (data.length == 13) { + parseBytes(data); + } + + } + } + + + @Override + boolean nextInitCmd(int stateNr) { + switch (stateNr) { + case 0: + // set scale units + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + byte[] setUnitCmd = new byte[]{(byte)0x06, (byte)0x04, (byte)0x00, (byte) selectedUser.scale_unit}; + writeBytes(WEIGHT_CUSTOM_SERVICE, WEIGHT_CUSTOM_CONFIG, setUnitCmd); + break; + case 1: + // set current time + Calendar currentDateTime = Calendar.getInstance(); + int year = currentDateTime.get(Calendar.YEAR); + byte month = (byte)(currentDateTime.get(Calendar.MONTH)+1); + byte day = (byte)currentDateTime.get(Calendar.DAY_OF_MONTH); + byte hour = (byte)currentDateTime.get(Calendar.HOUR_OF_DAY); + byte min = (byte)currentDateTime.get(Calendar.MINUTE); + byte sec = (byte)currentDateTime.get(Calendar.SECOND); + + byte[] dateTimeByte = {(byte)(year), (byte)(year >> 8), month, day, hour, min, sec, 0x03, 0x00, 0x00}; + + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_TIME_CHARACTERISTIC, dateTimeByte); + break; + case 2: + // set notification on for weight measurement history + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextBluetoothCmd(int stateNr) { + switch (stateNr) { + case 0: + // configure scale to get only last measurements + int uniqueNumber = getUniqueNumber(); + + byte[] userIdentifier = new byte[]{(byte)0x01, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); + break; + case 1: + // set notification off for weight measurement history + setNotificationOff(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 2: + // set notification on for weight measurement history + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 3: + // invoke receiving history data + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x02}); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextCleanUpCmd(int stateNr) { + + switch (stateNr) { + case 0: + // send stop command to mi scale + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, new byte[]{0x03}); + break; + case 1: + // acknowledge that you received the last history data + int uniqueNumber = getUniqueNumber(); + + byte[] userIdentifier = new byte[]{(byte)0x04, (byte)0xFF, (byte)0xFF, (byte) ((uniqueNumber & 0xFF00) >> 8), (byte) ((uniqueNumber & 0xFF) >> 0)}; + writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC, userIdentifier); + break; + case 2: + // set notification on for body composition measurement + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_BODY_COMPOSITION_MEASUREMENT, WEIGHT_MEASUREMENT_CONFIG); + break; + default: + return false; + } + + return true; + } + + private void parseBytes(byte[] weightBytes) { + try { + float weight = 0.0f; + + final byte ctrlByte0 = weightBytes[0]; + final byte ctrlByte1 = weightBytes[1]; + + final boolean isWeightRemoved = isBitSet(ctrlByte1, 7); + final boolean isDateInvalid = isBitSet(ctrlByte1, 6); + final boolean isStabilized = isBitSet(ctrlByte1, 5); + final boolean isLBSUnit = isBitSet(ctrlByte0, 0); + final boolean isCattyUnit = isBitSet(ctrlByte1, 6); + + if (isStabilized && !isWeightRemoved && !isDateInvalid) { + + final int year = ((weightBytes[3] & 0xFF) << 8) | (weightBytes[2] & 0xFF); + final int month = (int) weightBytes[4]; + final int day = (int) weightBytes[5]; + final int hours = (int) weightBytes[6]; + final int min = (int) weightBytes[7]; + final int sec = (int) weightBytes[8]; + + if (isLBSUnit || isCattyUnit) { + weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 100.0f; + } else { + weight = (float) (((weightBytes[12] & 0xFF) << 8) | (weightBytes[11] & 0xFF)) / 200.0f; + } + + String date_string = year + "/" + month + "/" + day + "/" + hours + "/" + min; + Date date_time = new SimpleDateFormat("yyyy/MM/dd/HH/mm").parse(date_string); + + // Is the year plausible? Check if the year is in the range of 20 years... + if (validateDate(date_time, 20)) { + ScaleData scaleBtData = new ScaleData(); + + scaleBtData.setWeight(weight); + scaleBtData.setDateTime(date_time); + + addScaleData(scaleBtData); + } else { + Log.e("BluetoothMiScale", "Invalid Mi scale weight year " + year); + } + } + } catch (ParseException e) { + setBtStatus(BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")"); + } + } + + private boolean validateDate(Date weightDate, int range) { + + Calendar currentDatePos = Calendar.getInstance(); + currentDatePos.add(Calendar.YEAR, range); + + Calendar currentDateNeg = Calendar.getInstance(); + currentDateNeg.add(Calendar.YEAR, -range); + + if (weightDate.before(currentDatePos.getTime()) && weightDate.after(currentDateNeg.getTime())) { + return true; + } + + return false; + } + + private int getUniqueNumber() { + int uniqueNumber; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + uniqueNumber = prefs.getInt("uniqueNumber", 0x00); + + if (uniqueNumber == 0x00) { + Random r = new Random(); + uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; + + prefs.edit().putInt("uniqueNumber", uniqueNumber).commit(); + } + + int userId = prefs.getInt("selectedUserId", -1); + + return uniqueNumber + userId; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java new file mode 100644 index 00000000..67916b55 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/BluetoothYunmaiSE.java @@ -0,0 +1,168 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ + +package com.health.openscale.core.bluetooth; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.health.openscale.core.OpenScale; +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +import java.util.Date; +import java.util.Random; +import java.util.UUID; + +public class BluetoothYunmaiSE extends BluetoothCommunication { + private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000ffe4-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_CMD_SERVICE = UUID.fromString("0000ffe5-0000-1000-8000-00805f9b34fb"); + private final UUID WEIGHT_CMD_CHARACTERISTIC = UUID.fromString("0000ffe9-0000-1000-8000-00805f9b34fb"); + + private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + public BluetoothYunmaiSE(Context context) { + super(context); + } + + @Override + public String deviceName() { + return "Yunmai SE"; + } + + @Override + public String defaultDeviceName() { + return "YUNMAI-ISSE-US"; + } + + @Override + public boolean checkDeviceName(String btDeviceName) { + if (btDeviceName.startsWith("YUNMAI-ISSE")) { + return true; + } + + return false; + } + + @Override + boolean nextInitCmd(int stateNr) { + switch (stateNr) { + case 0: + int user_id = getUniqueNumber(); + + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + byte sex = selectedUser.isMale() ? (byte)0x01 : (byte)0x02; + byte display_unit = selectedUser.scale_unit == 0 ? (byte) 0x01 : (byte) 0x02; + + byte[] user_add_or_query = new byte[]{(byte)0x0d, (byte)0x12, (byte)0x10, (byte)0x01, (byte)0x00, (byte) 0x00, (byte) ((user_id & 0xFF00) >> 8), (byte) ((user_id & 0xFF) >> 0), (byte)selectedUser.body_height, (byte)sex, (byte) selectedUser.getAge(), (byte) 0x55, (byte) 0x5a, (byte) 0x00, (byte)0x00, (byte) display_unit, (byte) 0x03, (byte) 0x00 }; + user_add_or_query[17] = xor_checksum(user_add_or_query); + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, user_add_or_query); + break; + case 1: + long unix_time = new Date().getTime() / 1000; + + byte[] set_time = new byte[]{(byte)0x0d, (byte) 0x0d, (byte) 0x11, (byte)((unix_time & 0xFF000000) >> 32), (byte)((unix_time & 0xFF0000) >> 16), (byte)((unix_time & 0xFF00) >> 8), (byte)((unix_time & 0xFF) >> 0), (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00}; + + set_time[12] = xor_checksum(set_time); + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, set_time); + break; + case 2: + setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG); + break; + case 3: + byte[] magic_bytes = new byte[]{(byte)0x0d, (byte)0x05, (byte)0x13, (byte)0x00, (byte)0x16}; + writeBytes(WEIGHT_CMD_SERVICE, WEIGHT_CMD_CHARACTERISTIC, magic_bytes); + break; + default: + return false; + } + + return true; + } + + @Override + boolean nextBluetoothCmd(int stateNr) { + return false; + } + + @Override + boolean nextCleanUpCmd(int stateNr) { + return false; + } + + @Override + public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) { + final byte[] data = gattCharacteristic.getValue(); + + if (data != null && data.length > 0) { + + // if finished weighting? + if (data[3] == 0x02) { + parseBytes(data); + } + } + } + + private void parseBytes(byte[] weightBytes) { + long unix_timestamp = ((weightBytes[5] & 0xFF) << 24) | ((weightBytes[6] & 0xFF) << 16) | ((weightBytes[7] & 0xFF) << 8) | (weightBytes[8] & 0xFF); + Date btDate = new Date(); + btDate.setTime(unix_timestamp*1000); + + float weight = (float) (((weightBytes[13] & 0xFF) << 8) | (weightBytes[14] & 0xFF)) / 100.0f; + + ScaleData scaleBtData = new ScaleData(); + + final ScaleUser selectedUser = OpenScale.getInstance(context).getSelectedScaleUser(); + + scaleBtData.setConvertedWeight(weight, selectedUser.scale_unit); + scaleBtData.setDateTime(btDate); + + addScaleData(scaleBtData); + } + + private int getUniqueNumber() { + int uniqueNumber; + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + uniqueNumber = prefs.getInt("uniqueNumber", 0x00); + + if (uniqueNumber == 0x00) { + Random r = new Random(); + uniqueNumber = r.nextInt(65535 - 100 + 1) + 100; + + prefs.edit().putInt("uniqueNumber", uniqueNumber).commit(); + } + + int userId = prefs.getInt("selectedUserId", -1); + + return uniqueNumber + userId; + } + + private byte xor_checksum(byte[] data) { + byte checksum = 0x00; + + for (int i=0; i +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFBJoN extends EstimatedFatMetric { + @Override + public String getName() { + return "British Journal of Nutrition (1991)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (data.getBMI(user.body_height) * 1.2f) + (user.getAge() * 0.23f) - 16.2f; + } + + return (data.getBMI(user.body_height) * 1.2f) + (user.getAge() * 0.23f) - 5.4f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java new file mode 100644 index 00000000..35b76bed --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFDeurenberg.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFDeurenberg extends EstimatedFatMetric { + @Override + public String getName() { + return "Deurenberg et. al (1998)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.getAge() >= 16) { + return (1.2f * data.getBMI(user.body_height)) + (0.23f*user.getAge()) - (10.8f*(1-user.gender)) - 5.4f; + } + + return (1.294f * data.getBMI(user.body_height)) + (0.20f*user.getAge()) - (11.4f*(1-user.gender)) - 8.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java new file mode 100644 index 00000000..81e2aa45 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFEddy.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFEddy extends EstimatedFatMetric { + @Override + public String getName() { + return "Eddy et. al (1976)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (1.281f* data.getBMI(user.body_height)) - 10.13f; + } + + return (1.48f* data.getBMI(user.body_height)) - 7.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java new file mode 100644 index 00000000..f0f31a8b --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagher.java @@ -0,0 +1,37 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFGallagher extends EstimatedFatMetric { + @Override + public String getName() { + return "Gallagher et. al [non-asian] (2000)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + // non-asian male + return 64.5f - 848.0f * (1.0f / data.getBMI(user.body_height)) + 0.079f * user.getAge() - 16.4f + 0.05f * user.getAge() + 39.0f * (1.0f / data.getBMI(user.body_height)); + } + + // non-asian female + return 64.5f - 848.0f * (1.0f / data.getBMI(user.body_height)) + 0.079f * user.getAge(); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java new file mode 100644 index 00000000..a6a986e2 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/BFGallagherAsian.java @@ -0,0 +1,37 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class BFGallagherAsian extends EstimatedFatMetric { + @Override + public String getName() { + return "Gallagher et. al [asian] (2000)"; + } + + @Override + public float getFat(ScaleUser user, ScaleData data) { + if (user.isMale()) { + // asian male + return 51.9f - 740.0f * (1.0f / data.getBMI(user.body_height)) + 0.029f * user.getAge(); + } + + // asian female + return 64.8f - 752.0f * (1.0f / data.getBMI(user.body_height)) + 0.016f * user.getAge(); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java new file mode 100644 index 00000000..6749a65e --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedFatMetric.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public abstract class EstimatedFatMetric { + public enum FORMULA_FAT { BF_DEURENBERG, BF_BJoN, BF_EDDY, BF_GALLAGHER, BF_GALLAGHER_ASIAN }; + + public static EstimatedFatMetric getEstimatedFatMetric( FORMULA_FAT fatMetric) { + switch (fatMetric) { + case BF_DEURENBERG: + return new BFDeurenberg(); + case BF_BJoN: + return new BFBJoN(); + case BF_EDDY: + return new BFEddy(); + case BF_GALLAGHER: + return new BFGallagher(); + case BF_GALLAGHER_ASIAN: + return new BFGallagherAsian(); + } + + return null; + } + + public abstract String getName(); + public abstract float getFat(ScaleUser user, ScaleData data); +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java new file mode 100644 index 00000000..a32e48db --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/EstimatedWaterMetric.java @@ -0,0 +1,41 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public abstract class EstimatedWaterMetric { + public enum FORMULA_WATER { TBW_BEHNKE, TBW_DELWAIDECRENIER, TBW_HUMEWEYERS, TBW_LEESONGKIM }; + + public static EstimatedWaterMetric getEstimatedWaterMetric( FORMULA_WATER waterMetric) { + switch (waterMetric) { + case TBW_BEHNKE: + return new TBWBehnke(); + case TBW_DELWAIDECRENIER: + return new TBWDelwaideCrenier(); + case TBW_HUMEWEYERS: + return new TBWHumeWeyers(); + case TBW_LEESONGKIM: + return new TBWLeeSongKim(); + } + + return null; + } + + public abstract String getName(); + public abstract float getWater(ScaleUser user, ScaleData data); +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java new file mode 100644 index 00000000..53a6ba54 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWBehnke.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWBehnke extends EstimatedWaterMetric { + @Override + public String getName() { + return "Behnke et. al (1963)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return 0.72f * (0.204f * user.body_height * user.body_height) / 100.0f; + } + + return 0.72f * (0.18f * user.body_height * user.body_height) / 100.0f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java new file mode 100644 index 00000000..83667a04 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWDelwaideCrenier.java @@ -0,0 +1,31 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWDelwaideCrenier extends EstimatedWaterMetric { + @Override + public String getName() { + return "Delwaide-Crenier et. al (1973)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + return 0.72f * (-1.976f + 0.907f * data.getWeight()); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java new file mode 100644 index 00000000..59d53c2d --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWHumeWeyers.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWHumeWeyers extends EstimatedWaterMetric { + @Override + public String getName() { + return "Hume & Weyers (1971)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return (0.194786f * user.body_height) + (0.296785f * data.getWeight()) - 14.012934f; + } + + return (0.34454f * user.body_height) + (0.183809f * data.getWeight()) - 35.270121f; + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java new file mode 100644 index 00000000..0f578345 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/core/bodymetric/TBWLeeSongKim.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2017 olie.xdev +* +* 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 +*/ +package com.health.openscale.core.bodymetric; + +import com.health.openscale.core.datatypes.ScaleData; +import com.health.openscale.core.datatypes.ScaleUser; + +public class TBWLeeSongKim extends EstimatedWaterMetric { + @Override + public String getName() { + return "Lee, Song, Kim, Lee et. al (1999)"; + } + + @Override + public float getWater(ScaleUser user, ScaleData data) { + if (user.isMale()) { + return -28.3497f + (0.243057f * user.body_height) + (0.366248f * data.getWeight()); + } + + return -26.6224f + (0.262513f * user.body_height) + (0.232948f * data.getWeight()); + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java index f7294be2..4131a399 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java +++ b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDatabase.java @@ -113,7 +113,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { values.put(COLUMN_NAME_BODY_HEIGHT, scaleUser.body_height); values.put(COLUMN_NAME_SCALE_UNIT, scaleUser.scale_unit); values.put(COLUMN_NAME_GENDER, scaleUser.gender); - values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.initial_weight); + values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.getInitialWeight()); values.put(COLUMN_NAME_GOAL_WEIGHT, scaleUser.goal_weight); values.put(COLUMN_NAME_GOAL_DATE, formatDateTime.format(scaleUser.goal_date)); @@ -146,7 +146,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { values.put(COLUMN_NAME_BODY_HEIGHT, scaleUser.body_height); values.put(COLUMN_NAME_SCALE_UNIT, scaleUser.scale_unit); values.put(COLUMN_NAME_GENDER, scaleUser.gender); - values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.initial_weight); + values.put(COLUMN_NAME_INITIAL_WEIGHT, scaleUser.getInitialWeight()); values.put(COLUMN_NAME_GOAL_WEIGHT, scaleUser.goal_weight); values.put(COLUMN_NAME_GOAL_DATE, formatDateTime.format(scaleUser.goal_date)); @@ -222,7 +222,7 @@ public class ScaleUserDatabase extends SQLiteOpenHelper { scaleUser.birthday = formatDateTime.parse(birthday); scaleUser.goal_date = formatDateTime.parse(goal_date); - scaleUser.initial_weight = Math.round(initial_weight * 100.0f) / 100.0f; + scaleUser.setInitialWeight(Math.round(initial_weight * 100.0f) / 100.0f); scaleUser.goal_weight = Math.round(goal_weight * 100.0f) / 100.0f; } catch (ParseException ex) { Log.e("ScaleDatabase", "Can't parse the date time string: " + ex.getMessage()); diff --git a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java index 7f0d5c88..d541685c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java +++ b/android_app/app/src/main/java/com/health/openscale/core/datatypes/ScaleUser.java @@ -21,6 +21,8 @@ import java.util.Date; public class ScaleUser { public static final String[] UNIT_STRING = new String[] {"kg", "lb", "st"}; + private static float KG_LB = 2.20462f; + private static float KG_ST = 0.157473f; public int id; public String user_name; @@ -28,7 +30,7 @@ public class ScaleUser { public int body_height; public int scale_unit; public int gender; - public float initial_weight; + private float initial_weight; public float goal_weight; public Date goal_date; @@ -62,6 +64,47 @@ public class ScaleUser { return userAge; } + public void setInitialWeight(float weight) { + this.initial_weight = weight; + + } + + public void setConvertedInitialWeight(float weight) { + switch (ScaleUser.UNIT_STRING[scale_unit]) { + case "kg": + this.initial_weight = weight; + break; + case "lb": + this.initial_weight = weight / KG_LB; + break; + case "st": + this.initial_weight = weight / KG_ST; + break; + } + } + + public float getInitialWeight() { + return initial_weight; + } + + public float getConvertedInitialWeight() { + float converted_weight = 0.0f; + + switch (ScaleUser.UNIT_STRING[scale_unit]) { + case "kg": + converted_weight = initial_weight; + break; + case "lb": + converted_weight = initial_weight * KG_LB; + break; + case "st": + converted_weight = initial_weight * KG_ST; + break; + } + + return converted_weight; + } + @Override public String toString() { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java index b208383b..ac99cc9a 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/UserSettingsActivity.java @@ -133,7 +133,7 @@ public class UserSettingsActivity extends Activity { txtBodyHeight.setText(Integer.toString(scaleUser.body_height)); txtBirthday.setText(dateFormat.format(scaleUser.birthday)); txtGoalDate.setText(dateFormat.format(scaleUser.goal_date)); - txtInitialWeight.setText(scaleUser.initial_weight+""); + txtInitialWeight.setText(Math.round(scaleUser.getConvertedInitialWeight()*100.0f)/100.0f + ""); txtGoalWeight.setText(scaleUser.goal_weight+""); switch (scaleUser.scale_unit) diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java index 03de804f..ef6c025b 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/MeasurementPreferences.java @@ -19,18 +19,37 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.widget.Toast; import com.health.openscale.R; import com.health.openscale.core.OpenScale; +import com.health.openscale.core.bodymetric.EstimatedFatMetric; +import com.health.openscale.core.bodymetric.EstimatedWaterMetric; -public class MeasurementPreferences extends PreferenceFragment { +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class MeasurementPreferences extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String PREFERENCE_KEY_DELETE_ALL = "deleteAll"; + public static final String PREFERENCE_KEY_ESTIMATE_FAT = "estimateFatEnable"; + public static final String PREFERENCE_KEY_ESTIMATE_FAT_FORMULA = "estimateFatFormula"; + public static final String PREFERENCE_KEY_ESTIMATE_WATER = "estimateWaterEnable"; + public static final String PREFERENCE_KEY_ESTIMATE_WATER_FORMULA = "estimateWaterFormula"; private Preference deleteAll; + private CheckBoxPreference estimateFatEnable; + private ListPreference estimateFatFormula; + private CheckBoxPreference estimateWaterEnable; + private ListPreference estimateWaterFormula; @Override public void onCreate(Bundle savedInstanceState) { @@ -40,6 +59,109 @@ public class MeasurementPreferences extends PreferenceFragment { deleteAll = (Preference) findPreference(PREFERENCE_KEY_DELETE_ALL); deleteAll.setOnPreferenceClickListener(new onClickListenerDeleteAll()); + + estimateFatEnable = (CheckBoxPreference) findPreference(PREFERENCE_KEY_ESTIMATE_FAT); + estimateFatFormula = (ListPreference) findPreference(PREFERENCE_KEY_ESTIMATE_FAT_FORMULA); + estimateWaterEnable = (CheckBoxPreference) findPreference(PREFERENCE_KEY_ESTIMATE_WATER); + estimateWaterFormula = (ListPreference) findPreference(PREFERENCE_KEY_ESTIMATE_WATER_FORMULA); + + updateFatListPreferences(); + updateWaterListPreferences(); + initSummary(getPreferenceScreen()); + } + + public void updateFatListPreferences() { + ArrayList listEntries = new ArrayList(); + ArrayList listEntryValues = new ArrayList(); + + for (EstimatedFatMetric.FORMULA_FAT formulaFat : EstimatedFatMetric.FORMULA_FAT.values()) { + EstimatedFatMetric fatMetric = EstimatedFatMetric.getEstimatedFatMetric(formulaFat); + + listEntries.add(fatMetric.getName()); + listEntryValues.add(formulaFat.toString()); + } + + estimateFatFormula.setEntries(listEntries.toArray(new CharSequence[listEntries.size()])); + estimateFatFormula.setEntryValues(listEntryValues.toArray(new CharSequence[listEntryValues.size()])); + } + + public void updateWaterListPreferences() { + ArrayList listEntries = new ArrayList(); + ArrayList listEntryValues = new ArrayList(); + + for (EstimatedWaterMetric.FORMULA_WATER formulaWater : EstimatedWaterMetric.FORMULA_WATER.values()) { + EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedWaterMetric(formulaWater); + + listEntries.add(waterMetric.getName()); + listEntryValues.add(formulaWater.toString()); + } + + estimateWaterFormula.setEntries(listEntries.toArray(new CharSequence[listEntries.size()])); + estimateWaterFormula.setEntryValues(listEntryValues.toArray(new CharSequence[listEntryValues.size()])); + } + + private void initSummary(Preference p) { + if (p instanceof PreferenceGroup) { + PreferenceGroup pGrp = (PreferenceGroup) p; + for (int i = 0; i < pGrp.getPreferenceCount(); i++) { + initSummary(pGrp.getPreference(i)); + } + } else { + updatePrefSummary(p); + } + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + updatePrefSummary(findPreference(key)); + } + + private void updatePrefSummary(Preference p) { + if (estimateFatEnable.isChecked()) { + estimateFatFormula.setEnabled(true); + } else { + estimateFatFormula.setEnabled(false); + } + + if (estimateWaterEnable.isChecked()) { + estimateWaterFormula.setEnabled(true); + } else { + estimateWaterFormula.setEnabled(false); + } + + estimateFatFormula.setSummary(EstimatedFatMetric.getEstimatedFatMetric(EstimatedFatMetric.FORMULA_FAT.valueOf(estimateFatFormula.getValue())).getName()); + estimateWaterFormula.setSummary(EstimatedWaterMetric.getEstimatedWaterMetric(EstimatedWaterMetric.FORMULA_WATER.valueOf(estimateWaterFormula.getValue())).getName()); + + if (p instanceof EditTextPreference) { + EditTextPreference editTextPref = (EditTextPreference) p; + if (p.getTitle().toString().contains("assword")) + { + p.setSummary("******"); + } else { + p.setSummary(editTextPref.getText()); + } + } + + if (p instanceof MultiSelectListPreference) { + MultiSelectListPreference editMultiListPref = (MultiSelectListPreference) p; + + CharSequence[] entries = editMultiListPref.getEntries(); + CharSequence[] entryValues = editMultiListPref.getEntryValues(); + List currentEntries = new ArrayList<>(); + Set currentEntryValues = editMultiListPref.getValues(); + + for (int i = 0; i < entries.length; i++) + if (currentEntryValues.contains(entryValues[i])) + currentEntries.add(entries[i].toString()); + + p.setSummary(currentEntries.toString()); + } } private class onClickListenerDeleteAll implements Preference.OnPreferenceClickListener { diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java index 447ba2ac..0d14a6a0 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/FatMeasurementView.java @@ -26,13 +26,27 @@ import com.health.openscale.core.evaluation.EvaluationSheet; public class FatMeasurementView extends MeasurementView { + private boolean estimateFatEnable; + public FatMeasurementView(Context context) { super(context, context.getResources().getString(R.string.label_fat), ContextCompat.getDrawable(context, R.drawable.ic_fat)); } + @Override + public boolean isEditable() { + if (estimateFatEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + return false; + } + return true; + } + @Override public void updateValue(ScaleData updateData) { - setValueOnView(updateData.getFat()); + if (estimateFatEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + setValueOnView((getContext().getString(R.string.label_automatic))); + } else { + setValueOnView(updateData.getFat()); + } } @Override @@ -48,6 +62,7 @@ public class FatMeasurementView extends MeasurementView { @Override public void updatePreferences(SharedPreferences preferences) { setVisible(preferences.getBoolean("fatEnable", true)); + estimateFatEnable = preferences.getBoolean("estimateFatEnable", false); } @Override diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java index 662e11ab..09635f65 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/MeasurementView.java @@ -21,16 +21,19 @@ import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.support.v4.content.ContextCompat; import android.text.Html; import android.text.InputType; import android.util.TypedValue; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Space; import android.widget.TableLayout; import android.widget.TableRow; @@ -59,6 +62,9 @@ public abstract class MeasurementView extends TableLayout { private ImageView iconView; private TextView nameView; private TextView valueView; + private LinearLayout incdecLayout; + private Button incView; + private Button decView; private ImageView editModeView; private ImageView indicatorView; @@ -90,17 +96,22 @@ public abstract class MeasurementView extends TableLayout { iconView = new ImageView(context); nameView = new TextView(context); valueView = new TextView(context); + incView = new Button(context); + decView = new Button(context); editModeView = new ImageView(context); indicatorView = new ImageView(context); evaluatorRow = new TableRow(context); evaluatorView = new LinearGaugeView(context); + incdecLayout = new LinearLayout(context); + measurementRow.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT, 1.0f)); measurementRow.setGravity(Gravity.CENTER); measurementRow.addView(iconView); measurementRow.addView(nameView); measurementRow.addView(valueView); + measurementRow.addView(incdecLayout); measurementRow.addView(editModeView); measurementRow.addView(indicatorView); @@ -113,7 +124,7 @@ public abstract class MeasurementView extends TableLayout { nameView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15); nameView.setTextColor(Color.BLACK); nameView.setLines(2); - nameView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.60f)); + nameView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 0.55f)); valueView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15); valueView.setTextColor(Color.BLACK); @@ -121,6 +132,50 @@ public abstract class MeasurementView extends TableLayout { valueView.setPadding(0,0,20,0); valueView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.MATCH_PARENT, 0.29f)); + incdecLayout.setOrientation(VERTICAL); + incdecLayout.addView(incView); + incdecLayout.addView(decView); + incdecLayout.setVisibility(View.GONE); + incdecLayout.setPadding(0,0,0,0); + incdecLayout.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.MATCH_PARENT, 0.05f)); + + incView.setText("+"); + incView.setBackgroundColor(Color.TRANSPARENT); + incView.setPadding(0,0,0,0); + incView.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.50f)); + incView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + incValue(); + } + }); + incView.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() { + @Override + public void onClick(View view) { + incValue(); + } + })); + incView.setVisibility(View.GONE); + + decView.setText("-"); + decView.setBackgroundColor(Color.TRANSPARENT); + decView.setPadding(0,0,0,0); + decView.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, 0, 0.50f)); + decView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + decValue(); + } + }); + + decView.setOnTouchListener(new RepeatListener(400, 100, new OnClickListener() { + @Override + public void onClick(View view) { + decValue(); + } + })); + decView.setVisibility(View.GONE); + editModeView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_editable)); editModeView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); editModeView.setVisibility(View.GONE); @@ -153,8 +208,27 @@ public abstract class MeasurementView extends TableLayout { if (value.length() == 0) { return -1; } + try { + return Float.valueOf(value); + } catch (NumberFormatException e) { + return -1; + } + } - return Float.valueOf(value); + public void incValue() { + float incValue = getValue() + 0.1f; + + if (incValue <= getMaxValue()) { + setValueOnView(incValue); + } + } + + public void decValue() { + float decValue = getValue() - 0.1f; + + if (decValue >= 0) { + setValueOnView(decValue); + } } public String getValueAsString() { @@ -176,13 +250,26 @@ public abstract class MeasurementView extends TableLayout { case VIEW: indicatorView.setVisibility(View.VISIBLE); editModeView.setVisibility(View.GONE); + incdecLayout.setVisibility(View.GONE); + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); break; case EDIT: case ADD: editModeView.setVisibility(View.VISIBLE); + incView.setVisibility(View.VISIBLE); + decView.setVisibility(View.VISIBLE); + incdecLayout.setVisibility(View.VISIBLE); if (!isEditable()) { editModeView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_noteeditable)); + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); + } + + if (getUnit() == null) { + incView.setVisibility(View.GONE); + decView.setVisibility(View.GONE); } indicatorView.setVisibility(View.GONE); @@ -334,7 +421,7 @@ public abstract class MeasurementView extends TableLayout { input.setInputType(getInputType()); input.setHint(getHintText()); input.setText(value); - input.setSelection(value.length()); + input.setSelectAllOnFocus(true); builder.setView(input); builder.setPositiveButton(getResources().getString(R.string.label_ok), null); @@ -396,5 +483,65 @@ public abstract class MeasurementView extends TableLayout { } } } + + private class RepeatListener implements OnTouchListener { + + private Handler handler = new Handler(); + + private int initialInterval; + private final int normalInterval; + private final OnClickListener clickListener; + + private Runnable handlerRunnable = new Runnable() { + @Override + public void run() { + handler.postDelayed(this, normalInterval); + clickListener.onClick(downView); + } + }; + + private View downView; + + /** + * RepeatListener cyclically runs a clickListener, emulating keyboard-like behaviour. First + * click is fired immediately, next one after the initialInterval, and subsequent ones after the normalInterval. + * + * @param initialInterval The interval after first click event + * @param normalInterval The interval after second and subsequent click events + * @param clickListener The OnClickListener, that will be called periodically + */ + public RepeatListener(int initialInterval, int normalInterval, + OnClickListener clickListener) { + if (clickListener == null) + throw new IllegalArgumentException("null runnable"); + if (initialInterval < 0 || normalInterval < 0) + throw new IllegalArgumentException("negative interval"); + + this.initialInterval = initialInterval; + this.normalInterval = normalInterval; + this.clickListener = clickListener; + } + + public boolean onTouch(View view, MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + handler.removeCallbacks(handlerRunnable); + handler.postDelayed(handlerRunnable, initialInterval); + downView = view; + downView.setPressed(true); + clickListener.onClick(view); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + handler.removeCallbacks(handlerRunnable); + downView.setPressed(false); + downView = null; + return true; + } + + return false; + } + + } } diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java index 73def5ff..9b1bd472 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/TimeMeasurementView.java @@ -70,7 +70,7 @@ public class TimeMeasurementView extends MeasurementView { @Override public String getUnit() { - return ""; + return null; } @Override diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java index a153aad8..54ec2de1 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/WaterMeasurementView.java @@ -26,13 +26,27 @@ import com.health.openscale.core.evaluation.EvaluationSheet; public class WaterMeasurementView extends MeasurementView { + private boolean estimateWaterEnable; + public WaterMeasurementView(Context context) { super(context, context.getResources().getString(R.string.label_water), ContextCompat.getDrawable(context, R.drawable.ic_water)); } + @Override + public boolean isEditable() { + if (estimateWaterEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + return false; + } + return true; + } + @Override public void updateValue(ScaleData updateData) { - setValueOnView(updateData.getWater()); + if (estimateWaterEnable && getMeasurementMode() == MeasurementViewMode.ADD) { + setValueOnView((getContext().getString(R.string.label_automatic))); + } else { + setValueOnView(updateData.getWater()); + } } @Override @@ -48,6 +62,7 @@ public class WaterMeasurementView extends MeasurementView { @Override public void updatePreferences(SharedPreferences preferences) { setVisible(preferences.getBoolean("waterEnable", true)); + estimateWaterEnable = preferences.getBoolean("estimateWaterEnable", false); } @Override diff --git a/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java b/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java index 658ef3a3..be7c1a32 100644 --- a/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java +++ b/android_app/app/src/main/java/com/health/openscale/gui/views/WeightMeasurementView.java @@ -34,10 +34,6 @@ public class WeightMeasurementView extends MeasurementView { @Override public void updateValue(ScaleData updateData) { setValueOnView(updateData.getConvertedWeight(getScaleUser().scale_unit)); - - if (getMeasurementMode() == MeasurementViewMode.ADD) { - getInputDialog().show(); - } } @Override diff --git a/android_app/app/src/main/res/values-de/strings.xml b/android_app/app/src/main/res/values-de/strings.xml index 4c2d4e59..c46a696b 100644 --- a/android_app/app/src/main/res/values-de/strings.xml +++ b/android_app/app/src/main/res/values-de/strings.xml @@ -135,4 +135,15 @@ Grundumsatz (BMR) Messe Gewicht: %.2f %1$.2f%2$s [%3$s] zu %4$s hinzugefügt + Maximale Anzahl gleichzeitiger Benutzer erreicht. + Bitte steigen Sie barfuß auf die Waage zur Referenzmessung + auto + Körpermetriken abschätzen + Anzeige + Messwertedatenbank + Verschiedenes + Körperfettschätzung + Körperfettformel + Körperwasserschätzung + Körperwasserformel \ No newline at end of file diff --git a/android_app/app/src/main/res/values-sk/strings.xml b/android_app/app/src/main/res/values-sk/strings.xml index 7adf126c..9a7b7292 100644 --- a/android_app/app/src/main/res/values-sk/strings.xml +++ b/android_app/app/src/main/res/values-sk/strings.xml @@ -104,7 +104,7 @@ Bod na údajový bod Potvrďte vymazanie Pripomienka - Pracovné dni + Dni v týždni Čas Text upozornenia Čas na váženie @@ -114,7 +114,7 @@ Vaše percento svalov v tele bolo Váš obvod pása bol Váš obvod bokov bol - do + v deň Pondelok Utorok Streda diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml index e649b79b..be1460db 100644 --- a/android_app/app/src/main/res/values/strings.xml +++ b/android_app/app/src/main/res/values/strings.xml @@ -120,6 +120,18 @@ Delete confirmation + Estimate body fat + Estimate body water + + Display + Body metrics estimation + Measurement database + Miscellaneous + + Body fat formula + Body water formula + auto + Reminder Weekdays Time diff --git a/android_app/app/src/main/res/xml/graph_preferences.xml b/android_app/app/src/main/res/xml/graph_preferences.xml index 4fa78e1a..327f2348 100644 --- a/android_app/app/src/main/res/xml/graph_preferences.xml +++ b/android_app/app/src/main/res/xml/graph_preferences.xml @@ -1,10 +1,14 @@ - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/android_app/app/src/main/res/xml/measurement_preferences.xml b/android_app/app/src/main/res/xml/measurement_preferences.xml index daa046dd..492a3bfb 100644 --- a/android_app/app/src/main/res/xml/measurement_preferences.xml +++ b/android_app/app/src/main/res/xml/measurement_preferences.xml @@ -1,10 +1,20 @@ - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file