1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-23 16:53:04 +02:00

added support for assisted weighing (e.g. baby or pets) were the weight is subtracted by a reference user weight, see issue #578

This commit is contained in:
oliexdev
2020-06-17 16:14:11 +02:00
parent 697ac276b4
commit 44580f04bf
10 changed files with 470 additions and 21 deletions

View File

@@ -0,0 +1,268 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "f0e80a69f9fff0c48f2b7df10de3228f",
"entities": [
{
"tableName": "scaleMeasurements",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `datetime` INTEGER, `weight` REAL NOT NULL, `fat` REAL NOT NULL, `water` REAL NOT NULL, `muscle` REAL NOT NULL, `visceralFat` REAL NOT NULL, `lbm` REAL NOT NULL, `waist` REAL NOT NULL, `hip` REAL NOT NULL, `bone` REAL NOT NULL, `chest` REAL NOT NULL, `thigh` REAL NOT NULL, `biceps` REAL NOT NULL, `neck` REAL NOT NULL, `caliper1` REAL NOT NULL, `caliper2` REAL NOT NULL, `caliper3` REAL NOT NULL, `calories` REAL NOT NULL, `comment` TEXT, FOREIGN KEY(`userId`) REFERENCES `scaleUsers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dateTime",
"columnName": "datetime",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "weight",
"columnName": "weight",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "fat",
"columnName": "fat",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "water",
"columnName": "water",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "muscle",
"columnName": "muscle",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "visceralFat",
"columnName": "visceralFat",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "lbm",
"columnName": "lbm",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "waist",
"columnName": "waist",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "hip",
"columnName": "hip",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "bone",
"columnName": "bone",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "chest",
"columnName": "chest",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "thigh",
"columnName": "thigh",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "biceps",
"columnName": "biceps",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "neck",
"columnName": "neck",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "caliper1",
"columnName": "caliper1",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "caliper2",
"columnName": "caliper2",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "caliper3",
"columnName": "caliper3",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "calories",
"columnName": "calories",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "comment",
"columnName": "comment",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_scaleMeasurements_userId_datetime",
"unique": true,
"columnNames": [
"userId",
"datetime"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_scaleMeasurements_userId_datetime` ON `${TABLE_NAME}` (`userId`, `datetime`)"
}
],
"foreignKeys": [
{
"table": "scaleUsers",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "scaleUsers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `username` TEXT NOT NULL, `birthday` INTEGER NOT NULL, `bodyHeight` REAL NOT NULL, `scaleUnit` INTEGER NOT NULL, `gender` INTEGER NOT NULL, `initialWeight` REAL NOT NULL, `goalWeight` REAL NOT NULL, `goalDate` INTEGER, `measureUnit` INTEGER NOT NULL, `activityLevel` INTEGER NOT NULL, `assistedWeighing` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "birthday",
"columnName": "birthday",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bodyHeight",
"columnName": "bodyHeight",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "scaleUnit",
"columnName": "scaleUnit",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "gender",
"columnName": "gender",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "initialWeight",
"columnName": "initialWeight",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "goalWeight",
"columnName": "goalWeight",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "goalDate",
"columnName": "goalDate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "measureUnit",
"columnName": "measureUnit",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "activityLevel",
"columnName": "activityLevel",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "assistedWeighing",
"columnName": "assistedWeighing",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f0e80a69f9fff0c48f2b7df10de3228f')"
]
}
}

View File

@@ -133,7 +133,7 @@ public class OpenScale {
db.setForeignKeyConstraintsEnabled(true);
}
})
.addMigrations(AppDatabase.MIGRATION_1_2, AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4)
.addMigrations(AppDatabase.MIGRATION_1_2, AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5)
.build();
measurementDAO = appDB.measurementDAO();
userDAO = appDB.userDAO();
@@ -288,6 +288,21 @@ public class OpenScale {
}
}
if (getScaleUser(scaleMeasurement.getUserId()).isAssistedWeighing()) {
int assistedWeighingRefUserId = prefs.getInt("assistedWeighingRefUserId", -1);
if (assistedWeighingRefUserId != -1) {
ScaleMeasurement lastRefScaleMeasurement = getLastScaleMeasurement(assistedWeighingRefUserId);
if (lastRefScaleMeasurement != null) {
float refWeight = lastRefScaleMeasurement.getWeight();
float diffToRef = scaleMeasurement.getWeight() - refWeight;
scaleMeasurement.setWeight(diffToRef);
}
} else {
Timber.e("assisted weighing reference user id is -1");
}
}
MeasurementViewSettings settings = new MeasurementViewSettings(prefs, WaterMeasurementView.KEY);
if (settings.isEnabled() && settings.isEstimationEnabled()) {
EstimatedWaterMetric waterMetric = EstimatedWaterMetric.getEstimatedMetric(

View File

@@ -16,17 +16,17 @@
package com.health.openscale.core.database;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
@Database(entities = {ScaleMeasurement.class, ScaleUser.class}, version = 4)
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
@Database(entities = {ScaleMeasurement.class, ScaleUser.class}, version = 5)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract ScaleMeasurementDAO measurementDAO();
@@ -176,4 +176,20 @@ public abstract class AppDatabase extends RoomDatabase {
}
}
};
public static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.beginTransaction();
try {
// Add assisted weighing to table
database.execSQL("ALTER TABLE scaleUsers ADD assistedWeighing INTEGER NOT NULL default 0");
database.setTransactionSuccessful();
}
finally {
database.endTransaction();
}
}
};
}

View File

@@ -56,10 +56,10 @@ public interface ScaleMeasurementDAO {
@Query("SELECT * FROM scaleMeasurements WHERE datetime >= :startYear AND datetime < :endYear AND userId = :userId AND enabled = 1 ORDER BY datetime DESC")
List<ScaleMeasurement> getAllInRange(Date startYear, Date endYear, int userId);
@Query("SELECT * FROM scaleMeasurements WHERE userId = :userId AND enabled = 1 ORDER BY datetime DESC LIMIT 1")
@Query("SELECT * FROM scaleMeasurements WHERE userId = :userId AND enabled = 1 ORDER BY datetime DESC LIMIT 0,1")
ScaleMeasurement getLatest(int userId);
@Query("SELECT * FROM scaleMeasurements WHERE userId = :userId AND enabled = 1 ORDER BY datetime ASC LIMIT 1")
@Query("SELECT * FROM scaleMeasurements WHERE userId = :userId AND enabled = 1 ORDER BY datetime ASC LIMIT 0,1")
ScaleMeasurement getFirst(int userId);
@Insert (onConflict = OnConflictStrategy.IGNORE)

View File

@@ -16,17 +16,17 @@
package com.health.openscale.core.datatypes;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.health.openscale.core.utils.Converters;
import com.health.openscale.core.utils.DateTimeHelpers;
import java.util.Calendar;
import java.util.Date;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "scaleUsers")
public class ScaleUser {
@PrimaryKey(autoGenerate = true)
@@ -38,7 +38,6 @@ public class ScaleUser {
@NonNull
@ColumnInfo(name = "birthday")
private Date birthday;
@NonNull
@ColumnInfo(name = "bodyHeight")
private float bodyHeight;
@ColumnInfo(name = "scaleUnit")
@@ -59,6 +58,8 @@ public class ScaleUser {
@NonNull
@ColumnInfo(name = "activityLevel")
private Converters.ActivityLevel activityLevel;
@ColumnInfo(name = "assistedWeighing")
private boolean assistedWeighing;
public ScaleUser() {
userName = "";
@@ -71,6 +72,7 @@ public class ScaleUser {
goalDate = new Date();
measureUnit = Converters.MeasureUnit.CM;
activityLevel = Converters.ActivityLevel.SEDENTARY;
assistedWeighing = false;
}
public int getId() {
@@ -177,6 +179,14 @@ public class ScaleUser {
return activityLevel;
}
public boolean isAssistedWeighing() {
return assistedWeighing;
}
public void setAssistedWeighing(boolean assistedWeighing) {
this.assistedWeighing = assistedWeighing;
}
public static String getPreferenceKey(int userId, String key) {
return String.format("user.%d.%s", userId, key);
}
@@ -191,9 +201,9 @@ public class ScaleUser {
return String.format(
"id(%d) name(%s) birthday(%s) age(%d) body height(%.2f) scale unit(%s) " +
"gender(%s) initial weight(%.2f) goal weight(%.2f) goal date(%s) " +
"measure unt(%s) activity level(%d)",
"measure unt(%s) activity level(%d) assisted weighing(%b)",
id, userName, birthday.toString(), getAge(), bodyHeight, scaleUnit.toString(),
gender.toString().toLowerCase(), initialWeight, goalWeight, goalDate.toString(),
measureUnit.toString(), activityLevel.toInt());
measureUnit.toString(), activityLevel.toInt(), assistedWeighing);
}
}

View File

@@ -25,6 +25,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -37,6 +38,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -60,12 +62,16 @@ import com.health.openscale.core.OpenScale;
import com.health.openscale.core.bluetooth.BluetoothCommunication;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import com.health.openscale.gui.measurement.MeasurementEntryFragment;
import com.health.openscale.gui.preferences.BluetoothSettingsFragment;
import com.health.openscale.gui.preferences.UserSettingsFragment;
import com.health.openscale.gui.slides.AppIntroActivity;
import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import cat.ereza.customactivityoncrash.config.CaocConfig;
@@ -360,18 +366,31 @@ public class MainActivity extends AppCompatActivity
return true;
}
if (OpenScale.getInstance().getSelectedScaleUser().isAssistedWeighing()) {
showAssistedWeighingDialog(true);
} else {
MobileNavigationDirections.ActionNavMobileNavigationToNavDataentry action = MobileNavigationDirections.actionNavMobileNavigationToNavDataentry();
action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.ADD);
action.setTitle(getString(R.string.label_add_measurement));
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(action);
}
return true;
case R.id.action_bluetooth_status:
if (OpenScale.getInstance().disconnectFromBluetoothDevice()) {
setBluetoothStatusIcon(R.drawable.ic_bluetooth_disabled);
}
else {
if (OpenScale.getInstance().getSelectedScaleUserId() == -1) {
showNoSelectedUserDialog();
return true;
}
if (OpenScale.getInstance().getSelectedScaleUser().isAssistedWeighing()) {
showAssistedWeighingDialog(false);
} else {
invokeConnectToBluetoothDevice();
}
}
return true;
case R.id.importData:
importCsvFile();
@@ -387,6 +406,95 @@ public class MainActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void showAssistedWeighingDialog(boolean manuelEntry) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setPadding(50, 50, 0, 0);
TextView title = new TextView(this);
title.setText(R.string.label_assisted_weighing);
title.setTextSize(24);
title.setTypeface(null, Typeface.BOLD);
TextView description = new TextView(this);
description.setPadding(0, 20, 0, 0);
description.setText(R.string.info_assisted_weighing_choose_reference_user);
linearLayout.addView(title);
linearLayout.addView(description);
builder.setCustomTitle(linearLayout);
List<ScaleUser> scaleUserList = OpenScale.getInstance().getScaleUserList();
ArrayList<String> infoTexts = new ArrayList<>();
ArrayList<Integer> userIds = new ArrayList<>();
int assistedWeighingRefUserId = prefs.getInt("assistedWeighingRefUserId", -1);
int checkedItem = 0;
for (ScaleUser scaleUser : scaleUserList) {
String singleInfoText = scaleUser.getUserName();
if (!scaleUser.isAssistedWeighing()) {
ScaleMeasurement lastRefScaleMeasurement = OpenScale.getInstance().getLastScaleMeasurement(scaleUser.getId());
if (lastRefScaleMeasurement != null) {
singleInfoText += " [" + Converters.fromKilogram(lastRefScaleMeasurement.getWeight(), scaleUser.getScaleUnit()) + scaleUser.getScaleUnit().toString() + "]";
} else {
singleInfoText += " [" + getString(R.string.label_empty) + "]";
}
infoTexts.add(singleInfoText);
userIds.add(scaleUser.getId());
}
if (scaleUser.getId() == assistedWeighingRefUserId) {
checkedItem = infoTexts.indexOf(singleInfoText);
}
}
if (!infoTexts.isEmpty()) {
builder.setSingleChoiceItems(infoTexts.toArray(new CharSequence[infoTexts.size()]), checkedItem, null);
} else {
builder.setMessage(getString(R.string.info_assisted_weighing_no_reference_user));
}
builder.setNegativeButton(R.string.label_cancel, null);
builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int selectedPosition = ((AlertDialog)dialog).getListView().getCheckedItemPosition();
prefs.edit().putInt("assistedWeighingRefUserId", userIds.get(selectedPosition)).commit();
ScaleMeasurement lastRefScaleMeasurement = OpenScale.getInstance().getLastScaleMeasurement(userIds.get(selectedPosition));
if (lastRefScaleMeasurement != null) {
Calendar calMinusOneDay = Calendar.getInstance();
calMinusOneDay.add(Calendar.DAY_OF_YEAR, -1);
if (calMinusOneDay.getTime().after(lastRefScaleMeasurement.getDateTime())) {
Toast.makeText(getApplicationContext(), getString(R.string.info_assisted_weighing_old_reference_measurement), Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(getApplicationContext(), getString(R.string.info_assisted_weighing_no_reference_measurements), Toast.LENGTH_LONG).show();
return;
}
if (manuelEntry) {
MobileNavigationDirections.ActionNavMobileNavigationToNavDataentry action = MobileNavigationDirections.actionNavMobileNavigationToNavDataentry();
action.setMode(MeasurementEntryFragment.DATA_ENTRY_MODE.ADD);
action.setTitle(getString(R.string.label_add_measurement));
navController.navigate(action);
} else {
invokeConnectToBluetoothDevice();
}
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.

View File

@@ -129,6 +129,9 @@ public class ChartMeasurementView extends LineChart {
}
setMeasurementList(openScale.getScaleMeasurementOfStartDate(lastMeasurementCalender.get(Calendar.YEAR), lastMeasurementCalender.get(Calendar.MONTH), lastMeasurementCalender.get(Calendar.DAY_OF_MONTH)));
} else {
clear();
return;
}
} else {
setMeasurementList(openScale.getScaleMeasurementList());

View File

@@ -28,6 +28,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.RadioGroup;
@@ -64,6 +65,7 @@ public class UserSettingsFragment extends Fragment {
private EditText txtGoalDate;
private RadioGroup radioScaleUnit;
private RadioGroup radioGender;
private CheckBox assistedWeighing;
private RadioGroup radioMeasurementUnit;
private Spinner spinnerActivityLevel;
@@ -88,6 +90,7 @@ public class UserSettingsFragment extends Fragment {
txtBodyHeight = root.findViewById(R.id.txtBodyHeight);
radioScaleUnit = root.findViewById(R.id.groupScaleUnit);
radioGender = root.findViewById(R.id.groupGender);
assistedWeighing = root.findViewById(R.id.asisstedWeighing);
radioMeasurementUnit = root.findViewById(R.id.groupMeasureUnit);
spinnerActivityLevel = root.findViewById(R.id.spinnerActivityLevel);
txtInitialWeight = root.findViewById(R.id.txtInitialWeight);
@@ -294,6 +297,8 @@ public class UserSettingsFragment extends Fragment {
break;
}
assistedWeighing.setChecked(scaleUser.isAssistedWeighing());
spinnerActivityLevel.setSelection(scaleUser.getActivityLevel().toInt());
}
@@ -442,6 +447,7 @@ public class UserSettingsFragment extends Fragment {
scaleUser.setActivityLevel(Converters.fromActivityLevelInt(
spinnerActivityLevel.getSelectedItemPosition()));
scaleUser.setGender(gender);
scaleUser.setAssistedWeighing(assistedWeighing.isChecked());
scaleUser.setInitialWeight(Converters.toKilogram(initial_weight, scale_unit));
scaleUser.setGoalWeight(Converters.toKilogram(goal_weight, scale_unit));
scaleUser.setGoalDate(goal_date);

View File

@@ -108,6 +108,24 @@
</RadioGroup>
</TableRow>
<TableRow
android:id="@+id/rowAssistedWeighing"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
android:id="@+id/lblAssistedWeighing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_assisted_weighing" />
<CheckBox
android:id="@+id/asisstedWeighing"
android:layout_height="wrap_content"
android:orientation="horizontal">
</CheckBox>
</TableRow>
<TableRow
android:id="@+id/rowMeasureUnit"
android:layout_weight="1"

View File

@@ -56,6 +56,7 @@
<string name="label_height">Height</string>
<string name="label_scale_unit">Scale unit</string>
<string name="label_gender">Gender</string>
<string name="label_assisted_weighing">Assisted weighing</string>
<string name="label_male">Male</string>
<string name="label_female">Female</string>
<string name="label_goal_weight">Goal weight</string>
@@ -85,6 +86,10 @@
<string name="info_is_enable">enabled</string>
<string name="info_is_not_enable">disabled</string>
<string name="info_is_not_available">not available</string>
<string name="info_assisted_weighing_no_reference_measurements">Reference user has no measurements</string>
<string name="info_assisted_weighing_old_reference_measurement">Warning last reference measurement is older than a day</string>
<string name="info_assisted_weighing_no_reference_user">No reference user available, please create one</string>
<string name="info_assisted_weighing_choose_reference_user">Choose the reference user which the weight is subtracted</string>
<string name="info_scale_low_battery">Low battery level (%d%%), please recharge or replace scale batteries</string>
<string name="info_bluetooth_try_connection">Connecting to:</string>
<string name="info_bluetooth_connection_lost">Bluetooth connection lost</string>