diff --git a/android_app/app/build.gradle b/android_app/app/build.gradle
index ed852f07..d0ea3d7f 100644
--- a/android_app/app/build.gradle
+++ b/android_app/app/build.gradle
@@ -60,7 +60,7 @@ android {
}
dependencies {
- implementation 'com.google.android.material:material:1.1.0-beta01'
+ implementation 'com.google.android.material:material:1.2.0-alpha01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
@@ -72,10 +72,12 @@ dependencies {
implementation 'com.github.weliem:blessed-android:0.10'
// CustomActivityOnCrash
implementation 'cat.ereza:customactivityoncrash:2.2.0'
+ // AppIntro
+ implementation 'com.github.AppIntro:AppIntro:5.1.0'
// Room
- implementation 'androidx.room:room-runtime:2.2.0'
- annotationProcessor 'androidx.room:room-compiler:2.2.0'
- androidTestImplementation 'androidx.room:room-testing:2.2.0'
+ implementation 'androidx.room:room-runtime:2.2.1'
+ annotationProcessor 'androidx.room:room-compiler:2.2.1'
+ androidTestImplementation 'androidx.room:room-testing:2.2.1'
// Timber
implementation 'com.jakewharton.timber:timber:4.7.1'
// Local unit tests
diff --git a/android_app/app/src/main/AndroidManifest.xml b/android_app/app/src/main/AndroidManifest.xml
index f1a01bce..a775fedc 100644
--- a/android_app/app/src/main/AndroidManifest.xml
+++ b/android_app/app/src/main/AndroidManifest.xml
@@ -38,6 +38,8 @@
+
+
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java
index a8a0d60f..3fa336d0 100644
--- a/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java
+++ b/android_app/app/src/main/java/com/health/openscale/gui/MainActivity.java
@@ -55,15 +55,15 @@ 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.gui.activities.AppIntroActivity;
import com.health.openscale.gui.activities.BaseAppCompatActivity;
+import com.health.openscale.gui.activities.BluetoothSettingsActivity;
import com.health.openscale.gui.activities.DataEntryActivity;
import com.health.openscale.gui.activities.SettingsActivity;
-import com.health.openscale.gui.activities.UserSettingsActivity;
import com.health.openscale.gui.fragments.GraphFragment;
import com.health.openscale.gui.fragments.OverviewFragment;
import com.health.openscale.gui.fragments.StatisticsFragment;
import com.health.openscale.gui.fragments.TableFragment;
-import com.health.openscale.gui.preferences.BluetoothPreferences;
import java.io.File;
import java.util.List;
@@ -150,9 +150,8 @@ public class MainActivity extends BaseAppCompatActivity
}
if (prefs.getBoolean("firstStart", true)) {
- Intent intent = new Intent(this, UserSettingsActivity.class);
- intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST);
- startActivity(intent);
+ Intent appIntroIntent = new Intent(this, AppIntroActivity.class);
+ startActivity(appIntroIntent);
prefs.edit().putBoolean("firstStart", false).apply();
}
@@ -479,9 +478,9 @@ public class MainActivity extends BaseAppCompatActivity
}
String deviceName = prefs.getString(
- BluetoothPreferences.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, "");
+ BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, "");
String hwAddress = prefs.getString(
- BluetoothPreferences.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "");
+ BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "");
if (!BluetoothAdapter.checkBluetoothAddress(hwAddress)) {
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java
new file mode 100644
index 00000000..b7efb31b
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/AppIntroActivity.java
@@ -0,0 +1,87 @@
+/* Copyright (C) 2019 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.gui.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.github.paolorotolo.appintro.AppIntro;
+import com.health.openscale.R;
+import com.health.openscale.core.OpenScale;
+import com.health.openscale.gui.slides.BluetoothIntroSlide;
+import com.health.openscale.gui.slides.MetricsIntroSlide;
+import com.health.openscale.gui.slides.OpenSourceIntroSlide;
+import com.health.openscale.gui.slides.PrivacyIntroSlide;
+import com.health.openscale.gui.slides.SupportIntroSlide;
+import com.health.openscale.gui.slides.UserIntroSlide;
+import com.health.openscale.gui.slides.WelcomeIntroSlide;
+
+public class AppIntroActivity extends AppIntro {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setBarColor(getResources().getColor(R.color.blue_normal));
+ setWizardMode(true);
+ setBackButtonVisibilityWithDone(true);
+
+ showSkipButton(true);
+
+ addSlide(WelcomeIntroSlide.newInstance(R.layout.slide_welcome));
+ addSlide(PrivacyIntroSlide.newInstance(R.layout.slide_privacy));
+ addSlide(UserIntroSlide.newInstance(R.layout.slide_user));
+ addSlide(OpenSourceIntroSlide.newInstance(R.layout.slide_opensource));
+ addSlide(BluetoothIntroSlide.newInstance(R.layout.slide_bluetooth));
+ addSlide(MetricsIntroSlide.newInstance(R.layout.slide_metrics));
+ addSlide(SupportIntroSlide.newInstance(R.layout.slide_support));
+ }
+
+ @Override
+ public void onSkipPressed(Fragment currentFragment) {
+ super.onSkipPressed(currentFragment);
+ finish();
+ checkUserCreation();
+ }
+
+ @Override
+ public void onDonePressed(Fragment currentFragment) {
+ super.onDonePressed(currentFragment);
+ finish();
+ checkUserCreation();
+ }
+
+ @Override
+ public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
+ super.onSlideChanged(oldFragment, newFragment);
+
+ if (newFragment instanceof WelcomeIntroSlide) {
+ showSkipButton(true);
+ } else {
+ showSkipButton(false);
+ }
+ }
+
+ private void checkUserCreation() {
+ if (OpenScale.getInstance().getSelectedScaleUserId() == -1) {
+ Intent intent = new Intent(this, UserSettingsActivity.class);
+ intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java
new file mode 100644
index 00000000..d69cbd9c
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/BluetoothSettingsActivity.java
@@ -0,0 +1,396 @@
+/* Copyright (C) 2019 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.gui.activities;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DatePickerDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanResult;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.graphics.drawable.DrawableCompat;
+
+import com.health.openscale.R;
+import com.health.openscale.core.OpenScale;
+import com.health.openscale.core.bluetooth.BluetoothCommunication;
+import com.health.openscale.core.bluetooth.BluetoothFactory;
+import com.health.openscale.core.datatypes.ScaleUser;
+import com.health.openscale.core.utils.Converters;
+import com.health.openscale.gui.preferences.BluetoothPreferences;
+import com.health.openscale.gui.utils.ColorUtil;
+import com.health.openscale.gui.utils.PermissionHelper;
+import com.welie.blessed.BluetoothCentral;
+import com.welie.blessed.BluetoothCentralCallback;
+import com.welie.blessed.BluetoothPeripheral;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import timber.log.Timber;
+
+public class BluetoothSettingsActivity extends BaseAppCompatActivity {
+ private Context context;
+
+ public static final int GET_SCALE_REQUEST = 150;
+
+ public static final String PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME = "btDeviceName";
+ public static final String PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS = "btHwAddress";
+
+ private Map foundDevices = new HashMap<>();
+
+ private LinearLayout deviceListView;
+ private TextView txtSearching;
+ private ProgressBar progressBar;
+ private Handler progressHandler;
+ private BluetoothCentral central;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_bluetoothsettings);
+ context = this;
+
+ deviceListView = findViewById(R.id.deviceListView);
+ txtSearching = findViewById(R.id.txtSearching);
+ progressBar = findViewById(R.id.progressBar);
+ Toolbar toolbar = findViewById(R.id.bluetoothSettingToolbar);
+ setSupportActionBar(toolbar);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(R.string.label_bluetooth_title);
+
+ if (PermissionHelper.requestBluetoothPermission(this)) {
+ if (PermissionHelper.requestLocationServicePermission(this)) {
+ startBluetoothDiscovery();
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ // Override the default behaviour in order to return to the correct fragment
+ // (e.g. the table view) and not always go to the overview.
+ case android.R.id.home:
+ stopBluetoothDiscovery();
+ onBackPressed();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private static final String formatDeviceName(String name, String address) {
+ if (name.isEmpty() || address.isEmpty()) {
+ return "-";
+ }
+ return String.format("%s [%s]", name, address);
+ }
+
+ private static final String formatDeviceName(BluetoothDevice device) {
+ return formatDeviceName(device.getName(), device.getAddress());
+ }
+
+ private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
+ @Override
+ public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
+ onDeviceFound(scanResult);
+ }
+ };
+
+ private void startBluetoothDiscovery() {
+ deviceListView.removeAllViews();
+ foundDevices.clear();
+
+ central = new BluetoothCentral(getApplicationContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
+ central.scanForPeripherals();
+
+ txtSearching.setVisibility(View.VISIBLE);
+ txtSearching.setText(R.string.label_bluetooth_searching);
+ progressBar.setVisibility(View.VISIBLE);
+
+ progressHandler = new Handler();
+
+ // Don't let the BLE discovery run forever
+ progressHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ stopBluetoothDiscovery();
+
+ txtSearching.setText(R.string.label_bluetooth_searching_finished);
+ progressBar.setVisibility(View.GONE);
+
+ BluetoothDeviceView notSupported = new BluetoothDeviceView(context);
+ notSupported.setDeviceName(getString(R.string.label_scale_not_supported));
+ notSupported.setSummaryText(getString(R.string.label_click_to_help_add_support));
+ notSupported.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent notSupportedIntent = new Intent(Intent.ACTION_VIEW);
+ notSupportedIntent.setData(
+ Uri.parse("https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale"));
+
+ startActivity(notSupportedIntent);
+ }
+ });
+ deviceListView.addView(notSupported);
+ }
+ }, 20 * 1000);
+ }
+
+ private void stopBluetoothDiscovery() {
+ if (progressHandler != null) {
+ progressHandler.removeCallbacksAndMessages(null);
+ progressHandler = null;
+ }
+
+ if (central != null) {
+ central.stopScan();
+ }
+ }
+
+ private void onDeviceFound(final ScanResult bleScanResult) {
+ BluetoothDevice device = bleScanResult.getDevice();
+
+ if (device.getName() == null || foundDevices.containsKey(device.getAddress())) {
+ return;
+ }
+
+ BluetoothDeviceView deviceView = new BluetoothDeviceView(this);
+ deviceView.setDeviceName(formatDeviceName(bleScanResult.getDevice()));
+
+ BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(this, device.getName());
+ if (btDevice != null) {
+ Timber.d("Found supported device %s (driver: %s)",
+ formatDeviceName(device), btDevice.driverName());
+ deviceView.setOnClickListener(new onClickListenerDeviceSelect());
+ deviceView.setDeviceAddress(device.getAddress());
+ deviceView.setIcon(R.drawable.ic_bluetooth_device_supported);
+ deviceView.setSummaryText(btDevice.driverName());
+ }
+ else {
+ Timber.d("Found unsupported device %s",
+ formatDeviceName(device));
+ deviceView.setIcon(R.drawable.ic_bluetooth_device_not_supported);
+ deviceView.setSummaryText(getString(R.string.label_bt_device_no_support));
+ deviceView.setEnabled(false);
+
+ if (OpenScale.DEBUG_MODE) {
+ deviceView.setEnabled(true);
+ deviceView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ getDebugInfo(device);
+ }
+ });
+ }
+ }
+
+ foundDevices.put(device.getAddress(), btDevice != null ? device : null);
+ deviceListView.addView(deviceView);
+ }
+
+ private class onClickListenerDeviceSelect implements View.OnClickListener {
+ @Override
+ public void onClick(View view) {
+ BluetoothDeviceView deviceView = (BluetoothDeviceView)view;
+
+ BluetoothDevice device = foundDevices.get(deviceView.getDeviceAddress());
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ prefs.edit()
+ .putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getAddress())
+ .putString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, device.getName())
+ .apply();
+
+ finishActivity(GET_SCALE_REQUEST);
+ }
+ }
+
+ private void getDebugInfo(final BluetoothDevice device) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Fetching info")
+ .setMessage("Please wait while we fetch extended info from your scale...")
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ OpenScale.getInstance().disconnectFromBluetoothDevice();
+ dialog.dismiss();
+ }
+ });
+
+ final AlertDialog dialog = builder.create();
+
+ Handler btHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (BluetoothCommunication.BT_STATUS.values()[msg.what]) {
+ case CONNECTION_LOST:
+ OpenScale.getInstance().disconnectFromBluetoothDevice();
+ dialog.dismiss();
+ break;
+ }
+ }
+ };
+
+ dialog.show();
+
+ String macAddress = device.getAddress();
+ stopBluetoothDiscovery();
+ OpenScale.getInstance().connectToBluetoothDeviceDebugMode(macAddress, btHandler);
+ }
+
+ private class BluetoothDeviceView extends LinearLayout {
+
+ private TextView deviceName;
+ private ImageView deviceIcon;
+ private String deviceAddress;
+
+ public BluetoothDeviceView(Context context) {
+ super(context);
+
+ deviceName = new TextView(context);
+ deviceName.setLines(2);
+ deviceIcon = new ImageView(context);
+
+ addView(deviceIcon);
+ addView(deviceName);
+ }
+
+ public void setDeviceAddress(String address) {
+ deviceAddress = address;
+ }
+
+ public String getDeviceAddress() {
+ return deviceAddress;
+ }
+
+ public void setDeviceName(String name) {
+ deviceName.setText(name);
+ }
+
+ public void setSummaryText(String text) {
+ SpannableStringBuilder stringBuilder = new SpannableStringBuilder(new String());
+
+ stringBuilder.append(deviceName.getText());
+ stringBuilder.append("\n");
+
+ int deviceNameLength = deviceName.getText().length();
+
+ stringBuilder.append(text);
+ stringBuilder.setSpan(new ForegroundColorSpan(Color.GRAY), deviceNameLength, deviceNameLength + text.length()+1,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ stringBuilder.setSpan(new RelativeSizeSpan(0.8f), deviceNameLength, deviceNameLength + text.length()+1,
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ deviceName.setText(stringBuilder);
+ }
+
+ public void setIcon(int resId) {
+ deviceIcon.setImageResource(resId);
+
+ int tintColor = ColorUtil.getTextColor(getApplicationContext());
+ deviceIcon.setColorFilter(tintColor, PorterDuff.Mode.SRC_IN);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener listener) {
+ super.setOnClickListener(listener);
+ deviceName.setOnClickListener(listener);
+ deviceIcon.setOnClickListener(listener);
+ }
+
+ @Override
+ public void setEnabled(boolean status) {
+ super.setEnabled(status);
+ deviceName.setEnabled(status);
+ deviceIcon.setEnabled(status);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == PermissionHelper.ENABLE_BLUETOOTH_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ if (PermissionHelper.requestBluetoothPermission(this)) {
+ startBluetoothDiscovery();
+ }
+ }
+ else {
+ onBackPressed();
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ switch (requestCode) {
+ case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (PermissionHelper.requestLocationServicePermission(this)) {
+ startBluetoothDiscovery();
+ }
+ } else {
+ Toast.makeText(this, R.string.permission_not_granted, Toast.LENGTH_SHORT).show();
+ onBackPressed();
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java b/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java
index 39340003..78681de0 100644
--- a/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java
+++ b/android_app/app/src/main/java/com/health/openscale/gui/activities/SettingsActivity.java
@@ -105,10 +105,6 @@ public class SettingsActivity extends PreferenceActivity
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
// TODO HACK to call RequestPermissionResult(...) in PreferenceFragment otherwise API level > 23 is required
switch(requestCode) {
- case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION:
- BluetoothPreferences bluetoothPreferences = (BluetoothPreferences)currentFragment;
- bluetoothPreferences.onMyOwnRequestPermissionsResult(requestCode, permissions, grantResults);
- break;
case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_READ_STORAGE:
case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_WRITE_STORAGE:
BackupPreferences backupPreferences = (BackupPreferences)currentFragment;
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java
index e3382cc1..bfc8bb1e 100644
--- a/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java
+++ b/android_app/app/src/main/java/com/health/openscale/gui/preferences/BluetoothPreferences.java
@@ -40,6 +40,8 @@ import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.bluetooth.BluetoothCommunication;
import com.health.openscale.core.bluetooth.BluetoothFactory;
+import com.health.openscale.gui.activities.BluetoothSettingsActivity;
+import com.health.openscale.gui.activities.UserSettingsActivity;
import com.health.openscale.gui.utils.ColorUtil;
import com.health.openscale.gui.utils.PermissionHelper;
import com.welie.blessed.BluetoothCentral;
@@ -51,18 +53,13 @@ import java.util.Map;
import timber.log.Timber;
+import static android.app.Activity.RESULT_OK;
+
public class BluetoothPreferences extends PreferenceFragment {
private static final String PREFERENCE_KEY_BLUETOOTH_SCANNER = "btScanner";
- public static final String PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME = "btDeviceName";
- public static final String PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS = "btHwAddress";
-
private PreferenceScreen btScanner;
- private Map foundDevices = new HashMap<>();
-
- private Handler progressHandler;
- private BluetoothCentral central;
private static final String formatDeviceName(String name, String address) {
if (name.isEmpty() || address.isEmpty()) {
@@ -71,141 +68,11 @@ public class BluetoothPreferences extends PreferenceFragment {
return String.format("%s [%s]", name, address);
}
- private static final String formatDeviceName(BluetoothDevice device) {
- return formatDeviceName(device.getName(), device.getAddress());
- }
-
private String getCurrentDeviceName() {
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
return formatDeviceName(
- prefs.getString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""),
- prefs.getString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""));
- }
-
- private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
- @Override
- public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
- onDeviceFound(scanResult);
- }
- };
-
- private void startBluetoothDiscovery() {
- foundDevices.clear();
- btScanner.removeAll();
-
- central = new BluetoothCentral(getActivity().getApplicationContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
- central.scanForPeripherals();
-
- final Preference scanning = new Preference(getActivity());
- scanning.setEnabled(false);
- btScanner.addPreference(scanning);
- btScanner.getPreference(0).setTitle(R.string.label_bluetooth_searching);
-
- progressHandler = new Handler();
-
- // Draw a simple progressbar during the discovery/scanning
- final int progressUpdatePeriod = 150;
- final String[] blocks = {"▏","▎","▍","▌","▋","▊","▉","█"};
- scanning.setSummary(blocks[0]);
- progressHandler.postDelayed(new Runnable() {
- int index = 1;
- @Override
- public void run() {
- String summary = scanning.getSummary()
- .subSequence(0, scanning.getSummary().length() - 1).toString();
- if (index == blocks.length) {
- summary += blocks[blocks.length - 1];
- index = 0;
- }
- summary += blocks[index++];
- scanning.setSummary(summary);
- progressHandler.postDelayed(this, progressUpdatePeriod);
- }
- }, progressUpdatePeriod);
-
- // Don't let the BLE discovery run forever
- progressHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- stopBluetoothDiscovery();
-
- Preference scanning = btScanner.getPreference(0);
- scanning.setTitle(R.string.label_bluetooth_searching_finished);
- scanning.setSummary("");
-
- Intent notSupportedIntent = new Intent(Intent.ACTION_VIEW);
- notSupportedIntent.setData(
- Uri.parse("https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale"));
-
- Preference notSupported = new Preference(getActivity());
- notSupported.setTitle(R.string.label_scale_not_supported);
- notSupported.setSummary(R.string.label_click_to_help_add_support);
- notSupported.setIntent(notSupportedIntent);
- btScanner.addPreference(notSupported);
- }
- }, 20 * 1000);
-
- // Abort discovery and scan if back is pressed or a scale is selected
- btScanner.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- stopBluetoothDiscovery();
- }
- });
- }
-
- private void stopBluetoothDiscovery() {
- if (progressHandler != null) {
- progressHandler.removeCallbacksAndMessages(null);
- progressHandler = null;
- }
-
- central.stopScan();
- }
-
- private void onDeviceFound(final ScanResult bleScanResult) {
- BluetoothDevice device = bleScanResult.getDevice();
-
- if (device.getName() == null || foundDevices.containsKey(device.getAddress())) {
- return;
- }
-
- Preference prefBtDevice = new Preference(getActivity());
- prefBtDevice.setTitle(formatDeviceName(bleScanResult.getDevice()));
-
- BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(getActivity(), device.getName());
- if (btDevice != null) {
- Timber.d("Found supported device %s (driver: %s)",
- formatDeviceName(device), btDevice.driverName());
- prefBtDevice.setOnPreferenceClickListener(new onClickListenerDeviceSelect());
- prefBtDevice.setKey(device.getAddress());
- prefBtDevice.setIcon(R.drawable.ic_bluetooth_device_supported);
- prefBtDevice.setSummary(btDevice.driverName());
-
- int tintColor = ColorUtil.getTextColor(getActivity().getApplicationContext());
- prefBtDevice.getIcon().setColorFilter(tintColor, PorterDuff.Mode.SRC_IN);
- }
- else {
- Timber.d("Found unsupported device %s",
- formatDeviceName(device));
- prefBtDevice.setIcon(R.drawable.ic_bluetooth_device_not_supported);
- prefBtDevice.setSummary(R.string.label_bt_device_no_support);
- prefBtDevice.setEnabled(false);
-
- if (OpenScale.DEBUG_MODE) {
- prefBtDevice.setEnabled(true);
- prefBtDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- getDebugInfo(device);
- return false;
- }
- });
- }
- }
-
- foundDevices.put(device.getAddress(), btDevice != null ? device : null);
- btScanner.addPreference(prefBtDevice);
+ prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""),
+ prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""));
}
private void updateBtScannerSummary() {
@@ -222,15 +89,11 @@ public class BluetoothPreferences extends PreferenceFragment {
btScanner = (PreferenceScreen) findPreference(PREFERENCE_KEY_BLUETOOTH_SCANNER);
- final Fragment fragment = this;
btScanner.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
- if (PermissionHelper.requestBluetoothPermission(getActivity(), fragment)) {
- if (PermissionHelper.requestLocationServicePermission(getActivity())) {
- startBluetoothDiscovery();
- }
- }
+ Intent intent = new Intent(preference.getContext(), BluetoothSettingsActivity.class);
+ startActivityForResult(intent, BluetoothSettingsActivity.GET_SCALE_REQUEST);
return true;
}
});
@@ -239,98 +102,15 @@ public class BluetoothPreferences extends PreferenceFragment {
// Dummy preference to make screen open
btScanner.addPreference(new Preference(getActivity()));
-
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- // Restart discovery after e.g. orientation change
- if (btScanner.getDialog() != null && btScanner.getDialog().isShowing()) {
- startBluetoothDiscovery();
- }
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == PermissionHelper.ENABLE_BLUETOOTH_REQUEST) {
- if (resultCode == Activity.RESULT_OK) {
- if (PermissionHelper.requestBluetoothPermission(getActivity(), this)) {
- startBluetoothDiscovery();
- }
- }
- else {
- btScanner.getDialog().dismiss();
- }
+ if (resultCode != RESULT_OK) {
+ return;
}
- }
-
- private void getDebugInfo(final BluetoothDevice device) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle("Fetching info")
- .setMessage("Please wait while we fetch extended info from your scale...")
- .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- OpenScale.getInstance().disconnectFromBluetoothDevice();
- dialog.dismiss();
- }
- });
-
- final AlertDialog dialog = builder.create();
-
- Handler btHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (BluetoothCommunication.BT_STATUS.values()[msg.what]) {
- case CONNECTION_LOST:
- OpenScale.getInstance().disconnectFromBluetoothDevice();
- dialog.dismiss();
- break;
- }
- }
- };
-
- dialog.show();
-
- String macAddress = device.getAddress();
- stopBluetoothDiscovery();
- OpenScale.getInstance().connectToBluetoothDeviceDebugMode(
- macAddress, btHandler);
- }
-
- private class onClickListenerDeviceSelect implements Preference.OnPreferenceClickListener {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- BluetoothDevice device = foundDevices.get(preference.getKey());
-
- preference.getSharedPreferences().edit()
- .putString(PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, device.getAddress())
- .putString(PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, device.getName())
- .apply();
-
+ if (requestCode == BluetoothSettingsActivity.GET_SCALE_REQUEST) {
updateBtScannerSummary();
-
- btScanner.getDialog().dismiss();
-
- return true;
- }
- }
-
- public void onMyOwnRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
- switch (requestCode) {
- case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- if (PermissionHelper.requestLocationServicePermission(getActivity())) {
- startBluetoothDiscovery();
- }
- } else {
- Toast.makeText(getActivity(), R.string.permission_not_granted, Toast.LENGTH_SHORT).show();
- btScanner.getDialog().dismiss();
- }
- break;
- }
}
}
}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java
new file mode 100644
index 00000000..fd730826
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/BluetoothIntroSlide.java
@@ -0,0 +1,106 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+import com.health.openscale.gui.activities.BluetoothSettingsActivity;
+
+import static android.app.Activity.RESULT_OK;
+
+public class BluetoothIntroSlide extends Fragment {
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ private Button btnSearchScale;
+ private TextView txtFoundDevice;
+
+ public static BluetoothIntroSlide newInstance(int layoutResId) {
+ BluetoothIntroSlide sampleSlide = new BluetoothIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ txtFoundDevice = view.findViewById(R.id.txtFoundDevice);
+ txtFoundDevice.setText(getCurrentDeviceName());
+
+ btnSearchScale = view.findViewById(R.id.btnSearchScale);
+ btnSearchScale.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(getContext(), BluetoothSettingsActivity.class);
+ startActivityForResult(intent, BluetoothSettingsActivity.GET_SCALE_REQUEST);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+ if (requestCode == BluetoothSettingsActivity.GET_SCALE_REQUEST) {
+ txtFoundDevice.setText(getCurrentDeviceName());
+ }
+ }
+
+ private final String formatDeviceName(String name, String address) {
+ if (name.isEmpty() || address.isEmpty()) {
+ return "[" + getContext().getString(R.string.label_empty) + "]";
+ }
+ return String.format("%s [%s]", name, address);
+ }
+
+ private String getCurrentDeviceName() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ return formatDeviceName(
+ prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, ""),
+ prefs.getString(BluetoothSettingsActivity.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, ""));
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java
new file mode 100644
index 00000000..439f732f
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/MetricsIntroSlide.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+
+public class MetricsIntroSlide extends Fragment {
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ private TextView slideMainText;
+
+ public static MetricsIntroSlide newInstance(int layoutResId) {
+ MetricsIntroSlide sampleSlide = new MetricsIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ slideMainText = view.findViewById(R.id.slideMainText);
+ slideMainText.setLinksClickable(true);
+ slideMainText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ return view;
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java
new file mode 100644
index 00000000..dff77219
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/OpenSourceIntroSlide.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+
+public class OpenSourceIntroSlide extends Fragment {
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ private TextView slideMainText;
+
+ public static OpenSourceIntroSlide newInstance(int layoutResId) {
+ OpenSourceIntroSlide sampleSlide = new OpenSourceIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ slideMainText = view.findViewById(R.id.slideMainText);
+ slideMainText.setLinksClickable(true);
+ slideMainText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ return view;
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java
new file mode 100644
index 00000000..c3d32ad7
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/PrivacyIntroSlide.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+
+public class PrivacyIntroSlide extends Fragment {
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ private TextView slideMainText;
+
+ public static PrivacyIntroSlide newInstance(int layoutResId) {
+ PrivacyIntroSlide sampleSlide = new PrivacyIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ slideMainText = view.findViewById(R.id.slideMainText);
+ slideMainText.setLinksClickable(true);
+ slideMainText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ return view;
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java
new file mode 100644
index 00000000..531b7b8c
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/SupportIntroSlide.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+
+public class SupportIntroSlide extends Fragment {
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ private TextView slideMainText;
+
+ public static SupportIntroSlide newInstance(int layoutResId) {
+ SupportIntroSlide sampleSlide = new SupportIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ slideMainText = view.findViewById(R.id.slideMainText);
+ slideMainText.setLinksClickable(true);
+ slideMainText.setMovementMethod(LinkMovementMethod.getInstance());
+
+ return view;
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java
new file mode 100644
index 00000000..4cef57ec
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/UserIntroSlide.java
@@ -0,0 +1,165 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.content.Intent;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.health.openscale.R;
+import com.health.openscale.core.OpenScale;
+import com.health.openscale.core.datatypes.ScaleUser;
+import com.health.openscale.gui.activities.UserSettingsActivity;
+
+import java.util.List;
+
+public class UserIntroSlide extends Fragment{
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+ private Button btnAddUser;
+ private TableLayout tblUsers;
+
+ public static UserIntroSlide newInstance(int layoutResId) {
+ UserIntroSlide sampleSlide = new UserIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(layoutResId, container, false);
+
+ btnAddUser = view.findViewById(R.id.btnAddUser);
+ tblUsers = view.findViewById(R.id.tblUsers);
+
+ btnAddUser.setOnClickListener(new onBtnAddUserClickListener());
+
+ updateTableUsers();
+
+ return view;
+ }
+
+ private class onBtnAddUserClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(getContext(), UserSettingsActivity.class);
+ intent.putExtra(UserSettingsActivity.EXTRA_MODE, UserSettingsActivity.ADD_USER_REQUEST);
+ startActivityForResult(intent, 100);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ updateTableUsers();
+ }
+
+ private void updateTableUsers() {
+ tblUsers.removeAllViews();
+ tblUsers.setStretchAllColumns(true);
+
+ List scaleUserList = OpenScale.getInstance().getScaleUserList();
+
+ TableRow header = new TableRow(getContext());
+
+ TextView headerUsername = new TextView(getContext());
+ headerUsername.setText(R.string.label_user_name);
+ headerUsername.setGravity(Gravity.CENTER_HORIZONTAL);
+ headerUsername.setTypeface(null, Typeface.BOLD);
+ header.addView(headerUsername);
+
+ TextView headAge = new TextView(getContext());
+ headAge.setText(R.string.label_age);
+ headAge.setGravity(Gravity.CENTER_HORIZONTAL);
+ headAge.setTypeface(null, Typeface.BOLD);
+ header.addView(headAge);
+
+ TextView headerGender = new TextView(getContext());
+ headerGender.setText(R.string.label_gender);
+ headerGender.setGravity(Gravity.CENTER_HORIZONTAL);
+ headerGender.setTypeface(null, Typeface.BOLD);
+ header.addView(headerGender);
+
+ tblUsers.addView(header);
+
+ if (!scaleUserList.isEmpty()) {
+ TableRow row = new TableRow(getContext());
+
+ for (ScaleUser scaleUser : scaleUserList) {
+ row = new TableRow(getContext());
+
+ TextView txtUsername = new TextView(getContext());
+ txtUsername.setText(scaleUser.getUserName());
+ txtUsername.setGravity(Gravity.CENTER_HORIZONTAL);
+ row.addView(txtUsername);
+
+ TextView txtAge = new TextView(getContext());
+ txtAge.setText(Integer.toString(scaleUser.getAge()));
+ txtAge.setGravity(Gravity.CENTER_HORIZONTAL);
+ row.addView(txtAge);
+
+ TextView txtGender = new TextView(getContext());
+ txtGender.setText((scaleUser.getGender().isMale()) ? getString(R.string.label_male) : getString(R.string.label_female));
+ txtGender.setGravity(Gravity.CENTER_HORIZONTAL);
+ row.addView(txtGender);
+
+ row.setGravity(Gravity.CENTER_HORIZONTAL);
+
+ tblUsers.addView(row);
+ }
+ } else {
+ TableRow row = new TableRow(getContext());
+
+ TextView txtEmpty = new TextView(getContext());
+ txtEmpty.setText("[" + getContext().getString(R.string.label_empty) + "]");
+ txtEmpty.setGravity(Gravity.CENTER_HORIZONTAL);
+ row.addView(txtEmpty);
+
+ row.setGravity(Gravity.CENTER_HORIZONTAL);
+
+ tblUsers.addView(row);
+ }
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java b/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java
new file mode 100644
index 00000000..f73e43c2
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/gui/slides/WelcomeIntroSlide.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2019 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.gui.slides;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+public class WelcomeIntroSlide extends Fragment {
+
+ private static final String ARG_LAYOUT_RES_ID = "layoutResId";
+ private int layoutResId;
+
+ public static WelcomeIntroSlide newInstance(int layoutResId) {
+ WelcomeIntroSlide sampleSlide = new WelcomeIntroSlide();
+
+ Bundle args = new Bundle();
+ args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
+ sampleSlide.setArguments(args);
+
+ return sampleSlide;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
+ layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(layoutResId, container, false);
+ }
+}
diff --git a/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java b/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java
index c40ae959..8d01ba21 100644
--- a/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java
+++ b/android_app/app/src/main/java/com/health/openscale/gui/utils/PermissionHelper.java
@@ -43,7 +43,7 @@ public class PermissionHelper {
public final static int ENABLE_BLUETOOTH_REQUEST = 5;
- public static boolean requestBluetoothPermission(final Activity activity, Fragment fragment) {
+ public static boolean requestBluetoothPermission(final Activity activity) {
final BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
@@ -52,7 +52,7 @@ public class PermissionHelper {
if (btAdapter != null) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- fragment.startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST);
+ activity.startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST);
}
return false;
}
diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png
new file mode 100644
index 00000000..52dd6daf
Binary files /dev/null and b/android_app/app/src/main/res/drawable-hdpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_opensource.png
new file mode 100644
index 00000000..53d93905
Binary files /dev/null and b/android_app/app/src/main/res/drawable-hdpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png
new file mode 100644
index 00000000..1792caef
Binary files /dev/null and b/android_app/app/src/main/res/drawable-hdpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-hdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-hdpi/ic_slide_support.png
new file mode 100644
index 00000000..1d9953ae
Binary files /dev/null and b/android_app/app/src/main/res/drawable-hdpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png
new file mode 100644
index 00000000..db69c82b
Binary files /dev/null and b/android_app/app/src/main/res/drawable-ldpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_opensource.png
new file mode 100644
index 00000000..1c649301
Binary files /dev/null and b/android_app/app/src/main/res/drawable-ldpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png
new file mode 100644
index 00000000..ff9de101
Binary files /dev/null and b/android_app/app/src/main/res/drawable-ldpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-ldpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-ldpi/ic_slide_support.png
new file mode 100644
index 00000000..d64dd4bf
Binary files /dev/null and b/android_app/app/src/main/res/drawable-ldpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_group.png
new file mode 100644
index 00000000..89fad028
Binary files /dev/null and b/android_app/app/src/main/res/drawable-mdpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png
new file mode 100644
index 00000000..aee10d5f
Binary files /dev/null and b/android_app/app/src/main/res/drawable-mdpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_privacy.png
new file mode 100644
index 00000000..7027baa2
Binary files /dev/null and b/android_app/app/src/main/res/drawable-mdpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png
new file mode 100644
index 00000000..e13a6efb
Binary files /dev/null and b/android_app/app/src/main/res/drawable-mdpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/drawable-xhdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_group.png
new file mode 100644
index 00000000..7748325d
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png
new file mode 100644
index 00000000..c86cc074
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-xhdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_privacy.png
new file mode 100644
index 00000000..4ed6fc31
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-xhdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_support.png
new file mode 100644
index 00000000..23167953
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xhdpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_group.png
new file mode 100644
index 00000000..30e18e57
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_opensource.png
new file mode 100644
index 00000000..cf731c86
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png
new file mode 100644
index 00000000..47f4edca
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_support.png
new file mode 100644
index 00000000..63d0bd79
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxhdpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_group.png b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_group.png
new file mode 100644
index 00000000..1eedcc21
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_group.png differ
diff --git a/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png
new file mode 100644
index 00000000..9f7feaa3
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_opensource.png differ
diff --git a/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_privacy.png b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_privacy.png
new file mode 100644
index 00000000..8d0a5a92
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_privacy.png differ
diff --git a/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_support.png b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_support.png
new file mode 100644
index 00000000..0721a99b
Binary files /dev/null and b/android_app/app/src/main/res/drawable-xxxhdpi/ic_slide_support.png differ
diff --git a/android_app/app/src/main/res/layout/activity_bluetoothsettings.xml b/android_app/app/src/main/res/layout/activity_bluetoothsettings.xml
new file mode 100644
index 00000000..71ed84bb
--- /dev/null
+++ b/android_app/app/src/main/res/layout/activity_bluetoothsettings.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android_app/app/src/main/res/layout/slide_bluetooth.xml b/android_app/app/src/main/res/layout/slide_bluetooth.xml
new file mode 100644
index 00000000..3250aaae
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_bluetooth.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_metrics.xml b/android_app/app/src/main/res/layout/slide_metrics.xml
new file mode 100644
index 00000000..2eaf5863
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_metrics.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_opensource.xml b/android_app/app/src/main/res/layout/slide_opensource.xml
new file mode 100644
index 00000000..e8b81f8b
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_opensource.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_privacy.xml b/android_app/app/src/main/res/layout/slide_privacy.xml
new file mode 100644
index 00000000..3daf4a03
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_privacy.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_support.xml b/android_app/app/src/main/res/layout/slide_support.xml
new file mode 100644
index 00000000..10dfb462
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_support.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_user.xml b/android_app/app/src/main/res/layout/slide_user.xml
new file mode 100644
index 00000000..cebcced6
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_user.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/layout/slide_welcome.xml b/android_app/app/src/main/res/layout/slide_welcome.xml
new file mode 100644
index 00000000..3950d6ee
--- /dev/null
+++ b/android_app/app/src/main/res/layout/slide_welcome.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml
index 6062e1e1..5acdfdfe 100644
--- a/android_app/app/src/main/res/values/strings.xml
+++ b/android_app/app/src/main/res/values/strings.xml
@@ -19,6 +19,7 @@
Yes
No
Delete
+ empty
Add user
Add measurement
Share
@@ -37,6 +38,7 @@
Waist-to-height ratio
Waist-hip ratio
Bone mass
+ Age
Smart User assignment
- day %d
@@ -233,4 +235,24 @@
Heavy
Extreme
Please upgrade to openScale pro for Bluetooth support
+
+ Welcome to\nopenScale
+ Open-source weight and body metrics tracker, with support for Bluetooth scales.
+ Protect your personal health data.
+ openScale doesn\'t send any data to a cloud and not having permission to access the internet is a strong guarantee of that.\n\nIf you really want to synchronise your weight to GoogleFit or with MQTT, you can install openScale sync.
+ Multiple users are supported.
+ openScale doesn\'t require you to create an online account.\n\nWe only need your user information to calculate some body metrics.
+ It\'s an open-source software with a high degree of transparency.
+ openScale is licensed under the GPLv3.\n\nAll body calculations are transparent and no hidden data transfer function or user identification are undertaken.\n\nYou can find the full source-code on GitHub. Convince yourself of openScale by reviewing it.
+ Various Bluetooth scales from different manufacturers are supported.
+ openScale has a built-in support for a number of Bluetooth scales from different manufacturers.\n\nWe constantly improve and extend the set of supported scales. You can find the full list and the level of support for each scale on GitHub.
+ Track and analyse your body metrics.
+ openScale supports over 22 body metrics.\n\nIt can be individual configured to only show the metrics you care about.\n\nIf your scale doesn\'t support body fat, body water and lean body mass, you can even estimate those measurements based on scientific publications.
+ Help us to improve openScale.
+ openScale needs you! If you found a bug, have an idea or a question or want to help to support your Bluetooth scale, please create a new issue.\n\nIf you would like to motivate the creator of this project in further development and continuous maintenance, feel free to give openScale a positive rating on GooglePlay or a star on GitHub.\n\nYour support and positive feedback is highly appreciated. Thank you!
+
+ SKIP
+ NEXT
+ BACK
+ DONE
\ No newline at end of file