1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-25 17:42:29 +02:00

Merge branch 'master' into RecyclerView

# Conflicts:
#	android_app/app/build.gradle
This commit is contained in:
oliexdev
2023-01-22 15:48:23 +01:00
171 changed files with 6573 additions and 1201 deletions

View File

@@ -2,15 +2,14 @@ apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android {
compileSdkVersion 29
compileSdkVersion 33
defaultConfig {
applicationId "com.health.openscale"
testApplicationId "com.health.openscale.test"
minSdkVersion 21
targetSdkVersion 29
versionCode 50
versionName "2.3.1"
archivesBaseName = "openScale-$versionName"
minSdkVersion 23
targetSdkVersion 33
versionCode 61
versionName "2.4.6"
manifestPlaceholders = [
appIcon: "@drawable/ic_launcher_openscale",
@@ -89,14 +88,23 @@ android {
buildTypes {
debug {
archivesBaseName = "openScale"
// don't include version number into the apk filename for debug build type so Travis can find it
applicationVariants.all { variant ->
variant.outputs.all { output ->
if (variant.buildType.name == "debug") {
outputFileName = "openScale-debug.apk"
}
}
}
}
release {
archivesBaseName = "openScale-"+defaultConfig.versionName
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
light {
archivesBaseName = "openScale-"+defaultConfig.versionName
manifestPlaceholders = [
appIcon: "@drawable/ic_launcher_openscale_light",
appIconRound: "@mipmap/ic_launcher_openscale_light_round"
@@ -108,6 +116,7 @@ android {
signingConfig signingConfigs.light
}
pro {
archivesBaseName = "openScale-"+defaultConfig.versionName
manifestPlaceholders = [
appIcon: "@drawable/ic_launcher_openscale_pro",
appIconRound: "@mipmap/ic_launcher_openscale_pro_round"
@@ -128,45 +137,44 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.material:material:1.8.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android:flexbox:0.3.2'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui:2.3.0'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:2.5.1"
// MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// Simple CSV
implementation 'com.j256.simplecsv:simplecsv:2.6'
// Blessed Android
implementation 'com.github.weliem:blessed-android:1.26'
implementation 'com.github.weliem:blessed-android:2.4.0'
// CustomActivityOnCrash
implementation 'cat.ereza:customactivityoncrash:2.2.0'
implementation 'cat.ereza:customactivityoncrash:2.3.0'
// AppIntro
implementation 'com.github.AppIntro:AppIntro:6.0.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.31'
// Room
implementation 'androidx.room:room-runtime:2.2.5'
annotationProcessor 'androidx.room:room-compiler:2.2.5'
androidTestImplementation 'androidx.room:room-testing:2.2.5'
implementation 'androidx.room:room-runtime:2.4.3'
annotationProcessor 'androidx.room:room-compiler:2.4.3'
androidTestImplementation 'androidx.room:room-testing:2.4.3'
// Timber
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.jakewharton.timber:timber:5.0.1'
// Local unit tests
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.2'
// Instrumented unit tests
androidTestImplementation 'androidx.annotation:annotation:1.1.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
implementation 'androidx.annotation:annotation:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0'
}
tasks.withType(Test) {

View File

@@ -2,12 +2,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.health.openscale" >
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Permission to allow read of data from the database through a ContentProvider.
Marked "dangerous" so that explicit user approval is required to read this data, not
@@ -31,23 +33,24 @@
<activity
android:name=".gui.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/AppTheme.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".gui.slides.AppIntroActivity" android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".gui.slides.SlideToNavigationAdapter" android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".gui.slides.AppIntroActivity" android:theme="@style/AppTheme.NoActionBar" android:exported="true"/>
<activity android:name=".gui.slides.SlideToNavigationAdapter" android:theme="@style/AppTheme.NoActionBar" android:exported="true"/>
<receiver android:name=".core.alarm.ReminderBootReceiver" android:enabled="false">
<receiver android:name=".core.alarm.ReminderBootReceiver" android:enabled="false" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".gui.widget.WidgetProvider">
<receiver android:name=".gui.widget.WidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
@@ -55,7 +58,7 @@
android:resource="@xml/widget_info" />
</receiver>
<activity android:name=".gui.widget.WidgetConfigure">
<activity android:name=".gui.widget.WidgetConfigure" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>

View File

@@ -77,6 +77,7 @@ public class OpenScale {
public static boolean DEBUG_MODE = false;
public static final String DATABASE_NAME = "openScale.db";
public static final float SMART_USER_ASSIGN_DEFAULT_RANGE = 15.0F;
private static OpenScale instance;
@@ -273,11 +274,7 @@ public class OpenScale {
// Check user id and do a smart user assign if option is enabled
if (scaleMeasurement.getUserId() == -1) {
if (prefs.getBoolean("smartUserAssign", false)) {
scaleMeasurement.setUserId(getSmartUserAssignment(scaleMeasurement.getWeight(), 15.0f));
} else {
scaleMeasurement.setUserId(getSelectedScaleUser().getId());
}
scaleMeasurement.setUserId(getAssignableUser(scaleMeasurement.getWeight()));
// don't add scale data if no user is selected
if (scaleMeasurement.getUserId() == -1) {
@@ -360,6 +357,20 @@ public class OpenScale {
return scaleMeasurement.getUserId();
}
public int getAssignableUser(float weight){
// Not the best function name
// Returns smart user assignment, if options allow it
// Otherwise it returns the selected user
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("smartUserAssign", false)) {
return getSmartUserAssignment(weight, SMART_USER_ASSIGN_DEFAULT_RANGE);
} else {
return getSelectedScaleUser().getId();
}
}
private int getSmartUserAssignment(float weight, float range) {
List<ScaleUser> scaleUsers = getScaleUserList();
Map<Float, Integer> inRangeWeights = new TreeMap<>();
@@ -641,6 +652,22 @@ public class OpenScale {
return true;
}
public boolean setBluetoothDeviceUserIndex(int appUserId, int scaleUserIndex, Handler uiHandler) {
if (btDeviceDriver == null) {
return false;
}
btDeviceDriver.selectScaleUserIndexForAppUserId(appUserId, scaleUserIndex, uiHandler);
return true;
}
public boolean setBluetoothDeviceUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
if (btDeviceDriver == null) {
return false;
}
btDeviceDriver.setScaleUserConsent(appUserId, scaleUserConsent, uiHandler);
return true;
}
public LiveData<List<ScaleMeasurement>> getScaleMeasurementsLiveData() {
int selectedUserId = getSelectedScaleUserId();

View File

@@ -75,7 +75,7 @@ public class AlarmBackupHandler
Intent alarmIntent = new Intent(context, ReminderBootReceiver.class);
alarmIntent.putExtra(INTENT_EXTRA_BACKUP_ALARM, true);
return PendingIntent.getBroadcast(context, ALARM_NOTIFICATION_ID, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return PendingIntent.getBroadcast(context, ALARM_NOTIFICATION_ID, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
public void disableAlarm(Context context) {
@@ -91,8 +91,7 @@ public class AlarmBackupHandler
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
// TODO to disable in the AndroidManfiest the requestLegacyExternalStorage on SDK >= 29 we need store the files on shared storages
File exportDir = new File(Environment.getExternalStorageDirectory(),
File exportDir = new File(context.getExternalFilesDir(null).getPath(),
prefs.getString("exportDir", "openScale Backup"));
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return;

View File

@@ -15,6 +15,8 @@
*/
package com.health.openscale.core.alarm;
import static android.content.Context.NOTIFICATION_SERVICE;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -25,6 +27,8 @@ import android.content.Intent;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import androidx.core.app.NotificationCompat;
import com.health.openscale.R;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.gui.MainActivity;
@@ -34,11 +38,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import androidx.core.app.NotificationCompat;
import timber.log.Timber;
import static android.content.Context.NOTIFICATION_SERVICE;
public class AlarmHandler
{
public static final String INTENT_EXTRA_ALARM = "alarmIntent";
@@ -116,7 +117,7 @@ public class AlarmHandler
Intent alarmIntent = new Intent(context, ReminderBootReceiver.class);
alarmIntent.putExtra(INTENT_EXTRA_ALARM, true);
return PendingIntent.getBroadcast(context, dayOfWeek, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return PendingIntent.getBroadcast(context, dayOfWeek, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
public void disableAllAlarms(Context context)
@@ -156,7 +157,7 @@ public class AlarmHandler
Intent notifyIntent = new Intent(context, MainActivity.class);
PendingIntent notifyPendingIntent =
PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "openScale_notify");

View File

@@ -0,0 +1,153 @@
/* Copyright (C) 2019 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
/*
* Based on source-code by weliem/blessed-android
*/
package com.health.openscale.core.bluetooth;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT16;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
import android.content.Context;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.welie.blessed.BluetoothBytesParser;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothBeurerBF105 extends BluetoothStandardWeightProfile {
private static final UUID SERVICE_BF105_CUSTOM = BluetoothGattUuid.fromShortCode(0xffff);
private static final UUID SERVICE_BF105_IMG = BluetoothGattUuid.fromShortCode(0xffc0);
private static final UUID CHARACTERISTIC_SCALE_SETTINGS = BluetoothGattUuid.fromShortCode(0x0000);
private static final UUID CHARACTERISTIC_USER_LIST = BluetoothGattUuid.fromShortCode(0x0001);
private static final UUID CHARACTERISTIC_INITIALS = BluetoothGattUuid.fromShortCode(0x0002);
private static final UUID CHARACTERISTIC_TARGET_WEIGHT = BluetoothGattUuid.fromShortCode(0x0003);
private static final UUID CHARACTERISTIC_ACTIVITY_LEVEL = BluetoothGattUuid.fromShortCode(0x0004);
private static final UUID CHARACTERISTIC_REFER_WEIGHT_BF = BluetoothGattUuid.fromShortCode(0x000b);
private static final UUID CHARACTERISTIC_BT_MODULE = BluetoothGattUuid.fromShortCode(0x0005);
private static final UUID CHARACTERISTIC_TAKE_MEASUREMENT = BluetoothGattUuid.fromShortCode(0x0006);
private static final UUID CHARACTERISTIC_TAKE_GUEST_MEASUREMENT = BluetoothGattUuid.fromShortCode(0x0007);
private static final UUID CHARACTERISTIC_BEURER_I = BluetoothGattUuid.fromShortCode(0x0008);
private static final UUID CHARACTERISTIC_UPPER_LOWER_BODY = CHARACTERISTIC_BEURER_I;
private static final UUID CHARACTERISTIC_BEURER_II = BluetoothGattUuid.fromShortCode(0x0009);
private static final UUID CHARACTERISTIC_BEURER_III = BluetoothGattUuid.fromShortCode(0x000a);
private static final UUID CHARACTERISTIC_IMG_IDENTIFY = BluetoothGattUuid.fromShortCode(0xffc1);
private static final UUID CHARACTERISTIC_IMG_BLOCK = BluetoothGattUuid.fromShortCode(0xffc2);
public BluetoothBeurerBF105(Context context) {
super(context);
}
@Override
public String driverName() {
return "Beurer BF105/720";
}
@Override
protected int getVendorSpecificMaxUserCount() {
return 10;
}
@Override
protected void writeUserDataToScale() {
writeTargetWeight();
super.writeUserDataToScale();
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
if (characteristic.equals(CHARACTERISTIC_USER_LIST)) {
handleVendorSpecificUserList(value);
}
else {
super.onBluetoothNotify(characteristic, value);
}
}
@Override
protected ScaleMeasurement bodyCompositionMeasurementToScaleMeasurement(byte[] value) {
ScaleMeasurement measurement = super.bodyCompositionMeasurementToScaleMeasurement(value);
float weight = measurement.getWeight();
if (weight == 0.f && previousMeasurement != null) {
weight = previousMeasurement.getWeight();
}
if (weight != 0.f) {
float water = Math.round(((measurement.getWater() / weight) * 10000.f))/100.f;
measurement.setWater(water);
}
return measurement;
}
@Override
protected void setNotifyVendorSpecificUserList() {
if (setNotificationOn(SERVICE_BF105_CUSTOM, CHARACTERISTIC_USER_LIST)) {
Timber.d("setNotifyVendorSpecificUserList() OK");
}
else {
Timber.d("setNotifyVendorSpecificUserList() FAILED");
}
}
@Override
protected synchronized void requestVendorSpecificUserList() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(0, FORMAT_UINT8);
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_USER_LIST,
parser.getValue());
}
@Override
protected void writeActivityLevel() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int activityLevel = this.selectedUser.getActivityLevel().toInt() + 1;
Timber.d(String.format("activityLevel: %d", activityLevel));
parser.setIntValue(activityLevel, FORMAT_UINT8);
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_ACTIVITY_LEVEL,
parser.getValue());
}
protected void writeTargetWeight() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int targetWeight = (int) this.selectedUser.getGoalWeight();
parser.setIntValue(targetWeight, FORMAT_UINT16);
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_TARGET_WEIGHT,
parser.getValue());
}
@Override
protected void writeInitials() {
BluetoothBytesParser parser = new BluetoothBytesParser();
String initials = getInitials(this.selectedUser.getUserName());
Timber.d("Initials: " + initials);
parser.setString(initials);
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_INITIALS,
parser.getValue());
}
@Override
protected synchronized void requestMeasurement() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(0, FORMAT_UINT8);
writeBytes(SERVICE_BF105_CUSTOM, CHARACTERISTIC_TAKE_MEASUREMENT,
parser.getValue());
}
}

View File

@@ -0,0 +1,119 @@
/* Copyright (C) 2019 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
/*
* Based on source-code by weliem/blessed-android
*/
package com.health.openscale.core.bluetooth;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
import android.content.Context;
import com.health.openscale.core.utils.Converters;
import com.welie.blessed.BluetoothBytesParser;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothBeurerBF600 extends BluetoothStandardWeightProfile {
private static final UUID SERVICE_BEURER_CUSTOM_BF600 = BluetoothGattUuid.fromShortCode(0xfff0);
private static final UUID CHARACTERISTIC_BEURER_BF600_SCALE_SETTING = BluetoothGattUuid.fromShortCode(0xfff1);
private static final UUID CHARACTERISTIC_BEURER_BF600_USER_LIST = BluetoothGattUuid.fromShortCode(0xfff2);
private static final UUID CHARACTERISTIC_BEURER_BF600_ACTIVITY_LEVEL = BluetoothGattUuid.fromShortCode(0xfff3);
private static final UUID CHARACTERISTIC_BEURER_BF600_TAKE_MEASUREMENT = BluetoothGattUuid.fromShortCode(0xfff4);
private static final UUID CHARACTERISTIC_BEURER_BF600_REFER_WEIGHT_BF = BluetoothGattUuid.fromShortCode(0xfff5);
private static final UUID CHARACTERISTIC_BEURER_BF850_INITIALS = BluetoothGattUuid.fromShortCode(0xfff6);
private String deviceName;
public BluetoothBeurerBF600(Context context, String name) {
super(context);
deviceName = name;
}
@Override
public String driverName() {
return "Beurer " + deviceName;
}
@Override
protected int getVendorSpecificMaxUserCount() {
return 8;
}
@Override
protected void writeActivityLevel() {
Converters.ActivityLevel al = selectedUser.getActivityLevel();
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0});
parser.setIntValue(al.toInt() + 1, FORMAT_UINT8, 0);
Timber.d(String.format("setCurrentUserData Activity level: %d", al.toInt() + 1));
writeBytes(SERVICE_BEURER_CUSTOM_BF600,
CHARACTERISTIC_BEURER_BF600_ACTIVITY_LEVEL, parser.getValue());
}
@Override
protected void writeInitials() {
if (haveCharacteristic(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF850_INITIALS)) {
BluetoothBytesParser parser = new BluetoothBytesParser();
String initials = getInitials(this.selectedUser.getUserName());
Timber.d("Initials: " + initials);
parser.setString(initials);
writeBytes(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF850_INITIALS,
parser.getValue());
}
}
@Override
protected void requestMeasurement() {
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0});
parser.setIntValue(0x00, FORMAT_UINT8, 0);
Timber.d(String.format("requestMeasurement BEURER 0xFFF4 magic: 0x00"));
writeBytes(SERVICE_BEURER_CUSTOM_BF600,
CHARACTERISTIC_BEURER_BF600_TAKE_MEASUREMENT, parser.getValue());
}
@Override
protected void setNotifyVendorSpecificUserList() {
if (setNotificationOn(SERVICE_BEURER_CUSTOM_BF600,
CHARACTERISTIC_BEURER_BF600_USER_LIST)) {
Timber.d("setNotifyVendorSpecificUserList() OK");
}
else {
Timber.d("setNotifyVendorSpecificUserList() FAILED");
}
}
@Override
protected synchronized void requestVendorSpecificUserList() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(0x00, FORMAT_UINT8);
writeBytes(SERVICE_BEURER_CUSTOM_BF600, CHARACTERISTIC_BEURER_BF600_USER_LIST,
parser.getValue());
stopMachineState();
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
if (characteristic.equals(CHARACTERISTIC_BEURER_BF600_USER_LIST)) {
handleVendorSpecificUserList(value);
}
else {
super.onBluetoothNotify(characteristic, value);
}
}
}

View File

@@ -0,0 +1,48 @@
/* Copyright (C) 2019 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
/*
* Based on source-code by weliem/blessed-android
*/
package com.health.openscale.core.bluetooth;
import android.content.Context;
import timber.log.Timber;
public class BluetoothBeurerBF950 extends BluetoothBeurerBF105 {
private String deviceName;
public BluetoothBeurerBF950(Context context, String name) {
super(context);
deviceName = name;
}
@Override
public String driverName() {
return deviceName;
}
@Override
protected int getVendorSpecificMaxUserCount() {
return 8;
}
@Override
protected void writeTargetWeight() {
Timber.d("Target Weight not supported on " + deviceName);
}
}

View File

@@ -38,6 +38,10 @@ import java.util.UUID;
import timber.log.Timber;
public class BluetoothBeurerSanitas extends BluetoothCommunication {
// < 0 means we are not actually waiting for data
// any value >= 0 means we are waiting for data in that state
private int waitForDataInStep = -1;
enum DeviceType { BEURER_BF700_800_RT_LIBRA, BEURER_BF710, SANITAS_SBF70_70 }
private static final UUID CUSTOM_SERVICE_1 = BluetoothGattUuid.fromShortCode(0xffe0);
@@ -61,9 +65,18 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
}
}
private class StoredData {
public byte[] measurementData = null;
public long storedUid = -1;
public long candidateUid = -1;
}
private ArrayList<RemoteUser> remoteUsers = new ArrayList<>();
private RemoteUser currentRemoteUser;
private byte[] measurementData;
private byte[] measurementData = null;
private StoredData storedMeasurement = new StoredData();
private boolean readyForData = false;
private boolean dataReceived = false;
private final int ID_START_NIBBLE_INIT = 6;
private final int ID_START_NIBBLE_CMD = 7;
@@ -171,26 +184,41 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// Fresh start, so reset everything
measurementData = null;
storedMeasurement.measurementData = null;
readyForData = false;
dataReceived = false;
// Setup notification
setNotificationOn(CUSTOM_SERVICE_1, CUSTOM_CHARACTERISTIC_WEIGHT);
break;
case 1:
// we will be waiting for data in state 1
waitForDataInStep = 1;
// Say "Hello" to the scale and wait for ack
Timber.d("Sending command: ID_START_NIBBLE_INIT");
sendAlternativeStartCode(ID_START_NIBBLE_INIT, (byte) 0x01);
stopMachineState();
break;
case 2:
// Update time on the scale (no ack)
long unixTime = System.currentTimeMillis() / 1000L;
Timber.d("Sending command: ID_START_NIBBLE_SET_TIME");
sendAlternativeStartCode(ID_START_NIBBLE_SET_TIME, Converters.toInt32Be(unixTime));
break;
case 3:
// We will be waiting for data in state 3
waitForDataInStep = 3;
// Request scale status and wait for ack
Timber.d("Sending command: CMD_SCALE_STATUS");
sendCommand(CMD_SCALE_STATUS, encodeUserId(null));
stopMachineState();
break;
case 4:
// We will be waiting for data in state 4
waitForDataInStep = 4;
// Request list of all users and wait until all have been received
Timber.d("Sending command: CMD_USER_LIST");
sendCommand(CMD_USER_LIST);
stopMachineState();
break;
@@ -209,10 +237,13 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
// Fetch saved measurements
if (currentRemoteUser != null) {
Timber.d("Request saved measurements for %s", currentRemoteUser.name);
// We will be waiting for data in state 5
waitForDataInStep = 5;
Timber.d("Request saved measurements (CMD_GET_SAVED_MEASUREMENTS) for %s", currentRemoteUser.name);
sendCommand(CMD_GET_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
stopMachineState();
}
// No user found, just continue to next step.
break;
case 6:
// Create a remote user for selected openScale user if needed
@@ -225,24 +256,47 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
}
}
if (currentRemoteUser == null) {
waitForDataInStep = 6;
createRemoteUser(selectedUser);
stopMachineState();
}
// Do not need to create new user, just continue to next step.
break;
case 7:
waitForDataInStep = 7;
Timber.d("Sending command: CMD_USER_DETAILS");
sendCommand(CMD_USER_DETAILS, encodeUserId(currentRemoteUser));
stopMachineState();
break;
case 8:
if (currentRemoteUser != null && !currentRemoteUser.isNew) {
// If we have unprocessed data available, store it now.
if( storedMeasurement.measurementData != null ) {
Timber.d("Reached state 8 (end) and still have saved data available. Storing now.");
if( currentRemoteUser != null ) {
Timber.i("User has been identified in the meantime, so store the data for them.");
addMeasurement(measurementData, currentRemoteUser.localUserId);
}
else {
Timber.i("User still not identified, so storing the data for the selected user.");
addMeasurement(measurementData, OpenScale.getInstance().getSelectedScaleUser().getId());
}
storedMeasurement.measurementData = null;
}
else if (!dataReceived && currentRemoteUser != null && !currentRemoteUser.isNew) {
// Looks like we never received a fresh measurement in this run, so request it now.
// Chances are not good that this will work, but let's try it anyway.
waitForDataInStep = 8;
Timber.d("Sending command: CMD_DO_MEASUREMENT");
sendCommand(CMD_DO_MEASUREMENT, encodeUserId(currentRemoteUser));
stopMachineState();
} else {
Timber.d("All finished, nothing to do.");
return false;
}
break;
default:
// Finish init if everything is done
Timber.d("End of state flow reached.");
return false;
}
@@ -253,11 +307,22 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
public void onBluetoothNotify(UUID characteristic, byte[] value) {
byte[] data = value;
if (data == null || data.length == 0) {
Timber.d("Received empty message.");
return;
}
if (data[0] == getAlternativeStartByte(ID_START_NIBBLE_INIT)) {
Timber.d("Got init ack from scale; scale is ready");
// this message should only happen in state 1
if( waitForDataInStep == 1 ) {
Timber.d("Received init ack (ID_START_NIBBLE_INIT) from scale; scale is ready");
}
else {
Timber.w("Received init ack (ID_START_NIBBLE_INIT) from scale in wrong state. Scale or app is confused. Continue in state 2.");
jumpNextToStepNr( 2 );
}
// All data received, no more waiting.
waitForDataInStep = -1;
// On to state 2
resumeMachineState();
return;
}
@@ -270,18 +335,23 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
try {
switch (data[1]) {
case CMD_USER_INFO:
Timber.d("Received: CMD_USER_INFO");
processUserInfo(data);
break;
case CMD_SAVED_MEASUREMENT:
Timber.d("Received: CMD_SAVED_MEASUREMENT");
processSavedMeasurement(data);
break;
case CMD_WEIGHT_MEASUREMENT:
Timber.d("Received: CMD_WEIGHT_MEASUREMENT");
processWeightMeasurement(data);
break;
case CMD_MEASUREMENT:
Timber.d("Received: CMD_MEASUREMENT");
processMeasurement(data);
break;
case CMD_SCALE_ACK:
Timber.d("Received: CMD_SCALE_ACK");
processScaleAck(data);
break;
default:
@@ -307,9 +377,13 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
Timber.d("Received user %d/%d: %s (%d)", current, count, name, year);
}
Timber.d("Sending ack for CMD_USER_INFO");
sendAck(data);
if (current != count) {
Timber.d("Not all users received, waiting for more...");
// More data should be incoming, so make sure we wait
stopMachineState();
return;
}
@@ -331,43 +405,134 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
}
}
if( waitForDataInStep != 4 ) {
Timber.w("Received final CMD_USER_INFO in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// All data received, no more waiting.
waitForDataInStep = -1;
// All users received
resumeMachineState();
}
private void processMeasurementData(byte[] data, int offset, boolean firstPart) {
private void processMeasurementData(byte[] data, int offset, boolean firstPart, boolean processingSavedMeasurements) {
if (firstPart) {
if( measurementData != null ) Timber.d("Discarding existing data.");
measurementData = Arrays.copyOfRange(data, offset, data.length);
return;
}
if( measurementData == null ) {
Timber.w("Received second measurement part without receiving first part before. Discarding data.");
return;
}
int oldEnd = measurementData.length;
int toCopy = data.length - offset;
measurementData = Arrays.copyOf(measurementData, oldEnd + toCopy);
System.arraycopy(data, offset, measurementData, oldEnd, toCopy);
addMeasurement(measurementData, currentRemoteUser.localUserId);
measurementData = null;
// Store data, but only if we are ready and know the user. Otherwise leave it for later.
if( currentRemoteUser != null && (readyForData || processingSavedMeasurements) ) {
Timber.d("Measurement complete, user identified and app ready: Storing data.");
addMeasurement(measurementData, currentRemoteUser.localUserId);
// Do we have unsaved data?
if( storedMeasurement.measurementData != null ) {
// Does it belong to the current user
if( currentRemoteUser.remoteUserId == storedMeasurement.storedUid ) {
// Does it have the same time stamp?
if( Converters.fromUnsignedInt32Be(measurementData, 0) == Converters.fromUnsignedInt32Be(storedMeasurement.measurementData, 0) ) {
// Then delete the unsaved data because it is already part of the received saved data
Timber.d("Discarding data saved for later, because it is already part of the received saved data from the scale.");
storedMeasurement.measurementData = null;
}
}
}
// Data processed, so discard it.
measurementData = null;
// Also discard saved data, because we got and processed new data
storedMeasurement.measurementData = null;
}
else if( !processingSavedMeasurements ) {
if( !readyForData ) {
Timber.d("New measurement complete, but not stored, because app not ready: Saving data for later.");
}
else {
Timber.d("New measurement complete, but not stored, because user not identified: Saving data for later.");
}
storedMeasurement.measurementData = measurementData;
storedMeasurement.storedUid = storedMeasurement.candidateUid;
}
else {
// How the f*** did we end up here?
Timber.e("Received saved measurement, but do not know for what user. This should not happen. Discarding data.");
measurementData = null;
}
}
private void processSavedMeasurement(byte[] data) {
int count = data[2] & 0xFF;
int current = data[3] & 0xFF;
Timber.d("Received part %d (of 2) of saved measurement %d of %d.", current % 2 == 1 ? 1 : 2, current / 2, count / 2);
processMeasurementData(data, 4, current % 2 == 1);
processMeasurementData(data, 4, current % 2 == 1, true);
Timber.d("Sending ack for CMD_SAVED_MEASUREMENT");
sendAck(data);
if (current == count) {
Timber.d("Deleting saved measurements for %s", currentRemoteUser.name);
sendCommand(CMD_DELETE_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
if (current != count) {
Timber.d("Not all parts / saved measurements received, waiting for more...");
// More data should be incoming, so make sure we wait
stopMachineState();
return;
}
if (currentRemoteUser.remoteUserId != remoteUsers.get(remoteUsers.size() - 1).remoteUserId) {
jumpNextToStepNr(5);
Timber.i("All saved measurements received.");
// This message should only be received in step 5
if( waitForDataInStep != 5 ) {
Timber.w("Received final CMD_SAVED_MEASUREMENT in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
resumeMachineState();
}
else {
Timber.w("...ignored, no data expected.");
}
// Let's not delete data we received unexpectedly, so just get out of here.
return;
}
// We are done with saved measurements, from now on we can process unrequested measurement data.
readyForData = true;
Timber.d("Deleting saved measurements (CMD_DELETE_SAVED_MEASUREMENTS) for %s", currentRemoteUser.name);
sendCommand(CMD_DELETE_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
// We sent a new command, so make sure we wait
stopMachineState();
/* Why do we want to resume the state machine, when we are not the last remote user?
* In the moment I do not understand this code, so I'll comment it out but leave it here for reference.
if (currentRemoteUser.remoteUserId != remoteUsers.get(remoteUsers.size() - 1).remoteUserId) {
// Only jump back to state 5 if we are in 5
if( jumpNextToStepNr( 5, 5 ) ) {
// Now resume
resumeMachineState();
}
}
*/
}
private void processWeightMeasurement(byte[] data) {
@@ -386,31 +551,74 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
private void processMeasurement(byte[] data) {
int count = data[2] & 0xFF;
int current = data[3] & 0xFF;
Timber.d("Received measurement part %d of %d.", current, count );
if (current == 1) {
long uid = decodeUserId(data, 5);
Timber.d("Receiving measurement data for remote UID %d.", uid);
// Remember uid in case we need it to save data for later.
storedMeasurement.candidateUid = uid;
// Now search for user
currentRemoteUser = null;
for (RemoteUser remoteUser : remoteUsers) {
if (remoteUser.remoteUserId == uid) {
currentRemoteUser = remoteUser;
Timber.d("Local user %s matches remote UID %d.", currentRemoteUser.name, uid);
break;
}
}
if( currentRemoteUser == null ) {
Timber.d("No local user identified for remote UID %d.", uid);
}
}
else {
processMeasurementData(data, 4, current == 2);
processMeasurementData(data, 4, current == 2, false);
}
// Even if we did not process the data, always ack the message
Timber.d("Sending ack for CMD_MEASUREMENT");
sendAck(data);
if (current == count) {
if (current != count) {
Timber.d("Not all measurement parts received, waiting for more...");
// More data should be incoming, so make sure we wait
stopMachineState();
return;
}
Timber.i("All measurement parts received.");
// Delete saved measurement, but only when we processed it before
if (currentRemoteUser != null && readyForData ) {
Timber.d("Sending command: CMD_DELETE_SAVED_MEASUREMENTS");
sendCommand(CMD_DELETE_SAVED_MEASUREMENTS, encodeUserId(currentRemoteUser));
// We sent a new command, so make sure we wait
stopMachineState();
}
// This message should only be received in step 6 and 8
else if( waitForDataInStep != 6 && waitForDataInStep != 8 ) {
Timber.w("Received final CMD_MEASUREMENT in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
resumeMachineState();
}
else {
Timber.w("...ignored, no data expected.");
}
}
else {
resumeMachineState();
}
}
private void processScaleAck(byte[] data) {
switch (data[2]) {
case CMD_SCALE_STATUS:
Timber.d("ACK type: CMD_SCALE_STATUS");
// data[3] != 0 if an invalid user id is given to the command,
// but it still provides some useful information (e.g. current unit).
final int batteryLevel = data[4] & 0xFF;
@@ -446,73 +654,224 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
break;
}
if (requestedUnit != currentUnit) {
Timber.d("Set scale unit to %s (%d)", user.getScaleUnit(), requestedUnit);
Timber.d("Set scale unit (CMD_SET_UNIT) to %s (%d)", user.getScaleUnit(), requestedUnit);
sendCommand(CMD_SET_UNIT, requestedUnit);
// We send a new command, so make sure we wait
stopMachineState();
} else {
// This should only be received in step 3
if( waitForDataInStep != 3 ) {
Timber.w("Received ACK for CMD_SCALE_STATUS in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// All data received, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
}
break;
case CMD_SET_UNIT:
Timber.d("ACK type: CMD_SET_UNIT");
if (data[3] == 0) {
Timber.d("Scale unit successfully set");
}
// This should only be received in step 3
if( waitForDataInStep != 3 ) {
Timber.w("Received ACK for CMD_SET_UNIT in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// All data received, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
break;
case CMD_USER_LIST:
Timber.d("ACK type: CMD_USER_LIST");
int userCount = data[4] & 0xFF;
int maxUserCount = data[5] & 0xFF;
Timber.d("Have %d users (max is %d)", userCount, maxUserCount);
if (userCount == 0) {
// We expect no more data, because there are no stored users.
// This message should only be received in state 4.
if( waitForDataInStep != 4 ) {
Timber.w("Received ACK for CMD_USER_LIST in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// User list is empty, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
}
// Otherwise wait for CMD_USER_INFO notifications
else {
// More data should be incoming, so make sure we wait
stopMachineState();
}
break;
case CMD_GET_SAVED_MEASUREMENTS:
Timber.d("ACK type: CMD_GET_SAVED_MEASUREMENTS");
int measurementCount = data[3] & 0xFF;
Timber.d("Received ACK for CMD_GET_SAVED_MEASUREMENTS for %d measurements.", measurementCount/2);
if (measurementCount == 0) {
// Skip delete all measurements step (since there are no measurements to delete)
Timber.d("No saved measurements found for user " + currentRemoteUser.name);
jumpNextToStepNr(5);
// We expect no more data, because there are no measurements.
readyForData = true;
// This message should only be received in step 5.
if( waitForDataInStep != 5 ) {
Timber.w("Received ACK for CMD_GET_SAVED_MEASUREMENTS in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// No saved data, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
}
// Otherwise wait for CMD_SAVED_MEASUREMENT notifications which will,
// once all measurements have been received, resume the state machine.
else {
// More data should be incoming, so make sure we wait
stopMachineState();
}
break;
case CMD_DELETE_SAVED_MEASUREMENTS:
Timber.d("ACK type: CMD_DELETE_SAVED_MEASUREMENTS");
if (data[3] == 0) {
Timber.d("Saved measurements successfully deleted for user " + currentRemoteUser.name);
}
// This message should only be received in state 5, 6 or 8
if( waitForDataInStep != 5 && waitForDataInStep != 6 && waitForDataInStep != 8 ) {
Timber.w("Received ACK for CMD_DELETE_SAVED_MEASUREMENTS in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// All data received, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
break;
case CMD_USER_ADD:
Timber.d("ACK type: CMD_USER_ADD");
// This message should only be received in state 6
if( waitForDataInStep != 6 ) {
Timber.w("Received ACK for CMD_USER_ADD in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
// No more data expected after this command.
waitForDataInStep = -1;
resumeMachineState();
// Get out of here, this wasn't supposed to happen.
break;
}
if (data[3] == 0) {
remoteUsers.add(currentRemoteUser);
// If we have unprocessed data available, store it now.
if( storedMeasurement.measurementData != null ) {
Timber.d("User identified, storing unprocessed data.");
addMeasurement(storedMeasurement.measurementData, currentRemoteUser.localUserId);
storedMeasurement.measurementData = null;
}
// We can now receive and process data, user has been identified and send to the scale.
readyForData = true;
// Try to start a measurement to make the scale learn the reference weight to recognize the user next time.
// If we already have data, this will most likely run into time-out and the scale switches off before finishing.
Timber.d("New user successfully added; time to step on scale");
sendMessage(R.string.info_step_on_scale_for_reference, 0);
remoteUsers.add(currentRemoteUser);
Timber.d("Sending command: CMD_DO_MEASUREMENT");
sendCommand(CMD_DO_MEASUREMENT, encodeUserId(currentRemoteUser));
// We send a new command, so make sure we wait
stopMachineState();
break;
}
Timber.d("Cannot create additional scale user (error 0x%02x)", data[3]);
sendMessage(R.string.error_max_scale_users, 0);
// Force disconnect
Timber.d("Send disconnect command to scale");
jumpNextToStepNr(8);
Timber.d("Terminating state machine.");
jumpNextToStepNr( 9 );
// All data received, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
break;
case CMD_DO_MEASUREMENT:
if (data[3] == 0) {
Timber.d("ACK type: CMD_DO_MEASUREMENT");
if (data[3] != 0) {
Timber.d("Measure command rejected.");
// We expect no more data, because measure command was not accepted.
// This message should only be received in state 6 or 8
if( waitForDataInStep != 6 && waitForDataInStep != 8 ) {
Timber.w("Received ACK for CMD_DO_MEASUREMENT in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
// No more data expected after this command.
waitForDataInStep = -1;
resumeMachineState();
// Get out of here, this wasn't supposed to happen.
break;
}
}
else {
Timber.d("Measure command successfully received");
sendMessage(R.string.info_step_on_scale, 0);
// More data should be incoming, so make sure we wait
stopMachineState();
}
break;
case CMD_USER_DETAILS:
Timber.d("ACK type: CMD_USER_DETAILS");
if (data[3] == 0) {
String name = decodeString(data, 4, 3);
int year = 1900 + (data[7] & 0xFF);
@@ -526,6 +885,21 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
Timber.d("Name: %s, Birthday: %d-%02d-%02d, Height: %d, Sex: %s, activity: %d",
name, year, month, day, height, male ? "male" : "female", activity);
}
// This message should only be received in state 7
if( waitForDataInStep != 7 ) {
Timber.w("Received ACK for CMD_USER_DETAILS in wrong state...");
if( waitForDataInStep >= 0 ){
Timber.w("...while waiting for other data. Retrying last step.");
// We are in the wrong state.
// This may happen, so let's just retry whatever we did before.
jumpBackOneStep();
}
else {
Timber.w("...ignored, no data expected.");
}
}
// All data received, no more waiting.
waitForDataInStep = -1;
resumeMachineState();
break;
@@ -628,6 +1002,7 @@ public class BluetoothBeurerSanitas extends BluetoothCommunication {
byte[] uid = encodeUserId(currentRemoteUser);
Timber.d("Sending command: CMD_USER_ADD");
sendCommand(CMD_USER_ADD, uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7],
nick[0], nick[1], nick[2], year, month, day, height, (byte) (sex | activity));
}

View File

@@ -16,6 +16,9 @@
package com.health.openscale.core.bluetooth;
import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
import static android.content.Context.LOCATION_SERVICE;
import android.Manifest;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.le.ScanResult;
@@ -29,20 +32,19 @@ import androidx.core.content.ContextCompat;
import com.health.openscale.R;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.welie.blessed.BluetoothCentral;
import com.welie.blessed.BluetoothCentralCallback;
import com.welie.blessed.BluetoothCentralManager;
import com.welie.blessed.BluetoothCentralManagerCallback;
import com.welie.blessed.BluetoothPeripheral;
import com.welie.blessed.BluetoothPeripheralCallback;
import com.welie.blessed.ConnectionState;
import com.welie.blessed.GattStatus;
import com.welie.blessed.HciStatus;
import com.welie.blessed.WriteType;
import java.util.UUID;
import timber.log.Timber;
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
import static android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
import static android.content.Context.LOCATION_SERVICE;
import static com.welie.blessed.BluetoothPeripheral.GATT_SUCCESS;
public abstract class BluetoothCommunication {
public enum BT_STATUS {
RETRIEVE_SCALE_DATA,
@@ -53,7 +55,9 @@ public abstract class BluetoothCommunication {
CONNECTION_LOST,
NO_DEVICE_FOUND,
UNEXPECTED_ERROR,
SCALE_MESSAGE
SCALE_MESSAGE,
CHOOSE_SCALE_USER,
ENTER_SCALE_USER_CONSENT,
}
private int stepNr;
@@ -64,7 +68,7 @@ public abstract class BluetoothCommunication {
private Handler callbackBtHandler;
private Handler disconnectHandler;
private BluetoothCentral central;
private BluetoothCentralManager central;
private BluetoothPeripheral btPeripheral;
public BluetoothCommunication(Context context)
@@ -73,7 +77,20 @@ public abstract class BluetoothCommunication {
this.disconnectHandler = new Handler();
this.stepNr = 0;
this.stopped = false;
this.central = new BluetoothCentral(context, bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
this.central = new BluetoothCentralManager(context, bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
}
protected boolean needReConnect() {
if (callbackBtHandler == null) {
return true;
}
if (btPeripheral != null) {
ConnectionState state = btPeripheral.getState();
if (state.equals(ConnectionState.CONNECTED) || state.equals(ConnectionState.CONNECTING)) {
return false;
}
}
return true;
}
/**
@@ -119,6 +136,20 @@ public abstract class BluetoothCommunication {
}
}
protected void chooseScaleUserUi(Object userList) {
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS.CHOOSE_SCALE_USER.ordinal(), userList).sendToTarget();
}
}
protected void enterScaleUserConsentUi(int appScaleUserId, int scaleUserIndex) {
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS.ENTER_SCALE_USER_CONSENT.ordinal(), appScaleUserId, scaleUserIndex).sendToTarget();
}
}
/**
* Send message to openScale user
*
@@ -162,22 +193,94 @@ public abstract class BluetoothCommunication {
*/
protected void onBluetoothDiscovery(BluetoothPeripheral peripheral) { }
/**
* Stopped current state machine
*/
protected synchronized void stopMachineState() {
Timber.d("Stop machine state");
stopped = true;
}
/**
* resume current state machine
*/
protected synchronized void resumeMachineState() {
Timber.d("Resume machine state");
stopped = false;
nextMachineStep();
}
/**
* This function only resumes the state machine if the current step equals curStep,
* i.e. if the next step (stepNr) is 1 above curStep.
*/
protected synchronized boolean resumeMachineState( int curStep ) {
if( curStep == stepNr-1 ) {
Timber.d("curStep " + curStep + " matches stepNr " + stepNr + "-1, resume state machine.");
stopped = false;
nextMachineStep();
return true;
}
else {
Timber.d("curStep " + curStep + " does not match stepNr " + stepNr + "-1, not resuming state machine.");
return false;
}
}
/**
* This function jump to a specific step number
* @param nr the step number which the state machine should jump to.
*/
protected synchronized void jumpNextToStepNr(int nr) {
Timber.d("Jump next to step nr " + nr);
stepNr = nr;
}
/**
* This function return the current step number
* @return the current step number
*/
protected synchronized int getStepNr() {
return stepNr;
}
/**
* This function jumps to the step newStepNr only if the current step equals curStepNr,
* i.e. if the next step (stepNr) is 1 above curStepNr
*/
protected synchronized boolean jumpNextToStepNr( int curStepNr, int newStepNr ) {
if( curStepNr == stepNr-1 ) {
Timber.d("curStepNr " + curStepNr + " matches stepNr " + stepNr + "-1, jumping next to step nr " + newStepNr);
stepNr = newStepNr;
return true;
}
else {
Timber.d("curStepNr " + curStepNr + " does not match stepNr " + stepNr + "-1, keeping next at step nr " + stepNr);
return false;
}
}
/**
* Call this function to decrement the current step counter of the state machine by one.
* Usually, if you call this function followed by resumeMachineState(), the current step will be repeated.
* Call multiple times to actually go back in time to previous steps.
*/
protected synchronized void jumpBackOneStep() {
stepNr--;
Timber.d("Jumped back one step to " + stepNr);
}
/**
* Check if specific characteristic exists on Bluetooth device.
*
* @param service the Bluetooth UUID service
* @param characteristic the Bluetooth UUID characteristic
* @return true if characteristic exists
*/
protected boolean haveCharacteristic(UUID service, UUID characteristic) {
return btPeripheral.getCharacteristic(service, characteristic) != null;
}
/**
* Write a byte array to a Bluetooth device.
*
@@ -198,7 +301,7 @@ public abstract class BluetoothCommunication {
protected void writeBytes(UUID service, UUID characteristic, byte[] bytes, boolean noResponse) {
Timber.d("Invoke write bytes [" + byteInHex(bytes) + "] on " + BluetoothGattUuid.prettyPrint(characteristic));
btPeripheral.writeCharacteristic(btPeripheral.getCharacteristic(service, characteristic), bytes,
noResponse ? WRITE_TYPE_NO_RESPONSE : WRITE_TYPE_DEFAULT);
noResponse ? WriteType.WITHOUT_RESPONSE : WriteType.WITH_RESPONSE);
}
/**
@@ -231,14 +334,27 @@ public abstract class BluetoothCommunication {
* Set notification flag on for the Bluetooth device.
*
* @param characteristic the Bluetooth UUID characteristic
* @return true if the operation was enqueued, false if the characteristic doesn't support notification or indications or
*/
protected void setNotificationOn(UUID service, UUID characteristic) {
protected boolean setNotificationOn(UUID service, UUID characteristic) {
Timber.d("Invoke set notification on " + BluetoothGattUuid.prettyPrint(characteristic));
if(btPeripheral.getService(service) != null) {
stopMachineState();
BluetoothGattCharacteristic currentTimeCharacteristic = btPeripheral.getCharacteristic(service, characteristic);
btPeripheral.setNotify(currentTimeCharacteristic, true);
if (currentTimeCharacteristic != null) {
boolean notifySet;
try {
notifySet = btPeripheral.setNotify(currentTimeCharacteristic, true);
}
catch (IllegalArgumentException e){
notifySet = false;
};
if (notifySet) {
stopMachineState();
return true;
}
}
}
return false;
}
/**
@@ -260,6 +376,25 @@ public abstract class BluetoothCommunication {
disconnectHandler.removeCallbacksAndMessages(null);
}
public void selectScaleUserIndexForAppUserId(int appUserId, int scaleUserIndex, Handler uiHandler) {
Timber.d("Set scale user index for app user id: Not implemented!");
}
public void setScaleUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
Timber.d("Set scale user consent for app user id: Not implemented!");
}
// +++
public byte[] getScaleMacAddress() {
String[] mac = btPeripheral.getAddress().split(":");
byte[] macAddress = new byte[6];
for(int i = 0; i < mac.length; i++) {
macAddress[i] = Integer.decode("0x" + mac[i]).byteValue();
}
return macAddress;
}
// ---
/**
* Convert a byte array to hex for debugging purpose
*
@@ -330,8 +465,8 @@ public abstract class BluetoothCommunication {
}
@Override
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, int status) {
if( status == GATT_SUCCESS) {
public void onNotificationStateUpdate(BluetoothPeripheral peripheral, BluetoothGattCharacteristic characteristic, GattStatus status) {
if( status.value == GATT_SUCCESS) {
if(peripheral.isNotifying(characteristic)) {
Timber.d(String.format("SUCCESS: Notify set for %s", characteristic.getUuid()));
resumeMachineState();
@@ -342,8 +477,8 @@ public abstract class BluetoothCommunication {
}
@Override
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, int status) {
if( status == GATT_SUCCESS) {
public void onCharacteristicWrite(BluetoothPeripheral peripheral, byte[] value, BluetoothGattCharacteristic characteristic, GattStatus status) {
if( status.value == GATT_SUCCESS) {
Timber.d(String.format("SUCCESS: Writing <%s> to <%s>", byteInHex(value), characteristic.getUuid().toString()));
nextMachineStep();
@@ -353,14 +488,14 @@ public abstract class BluetoothCommunication {
}
@Override
public void onCharacteristicUpdate(final BluetoothPeripheral peripheral, byte[] value, final BluetoothGattCharacteristic characteristic, final int status) {
public void onCharacteristicUpdate(final BluetoothPeripheral peripheral, byte[] value, final BluetoothGattCharacteristic characteristic, GattStatus status) {
resetDisconnectTimer();
onBluetoothNotify(characteristic.getUuid(), value);
}
};
// Callback for central
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
private final BluetoothCentralManagerCallback bluetoothCentralCallback = new BluetoothCentralManagerCallback() {
@Override
public void onConnectedPeripheral(BluetoothPeripheral peripheral) {
@@ -372,18 +507,18 @@ public abstract class BluetoothCommunication {
}
@Override
public void onConnectionFailed(BluetoothPeripheral peripheral, final int status) {
Timber.e(String.format("connection '%s' failed with status %d", peripheral.getName(), status ));
public void onConnectionFailed(BluetoothPeripheral peripheral, HciStatus status) {
Timber.e(String.format("connection '%s' failed with status %d", peripheral.getName(), status.value));
setBluetoothStatus(BT_STATUS.CONNECTION_LOST);
if (status == 8) {
if (status.value == 8) {
sendMessage(R.string.info_bluetooth_connection_error_scale_offline, 0);
}
}
@Override
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, final int status) {
Timber.d(String.format("disconnected '%s' with status %d", peripheral.getName(), status));
public void onDisconnectedPeripheral(final BluetoothPeripheral peripheral, HciStatus status) {
Timber.d(String.format("disconnected '%s' with status %d", peripheral.getName(), status.value));
}
@Override
@@ -409,9 +544,10 @@ public abstract class BluetoothCommunication {
// Otherwise the connection almost never succeeds.
LocationManager locationManager = (LocationManager)context.getSystemService(LOCATION_SERVICE);
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED && (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
if ((ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED ) &&
(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
(locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)))
) {
Timber.d("Do LE scan before connecting to device");
central.scanForPeripheralsWithAddresses(new String[]{macAddress});
@@ -439,6 +575,20 @@ public abstract class BluetoothCommunication {
}, 1000);
}
protected boolean reConnectPreviousPeripheral(Handler uiHandler) {
if (btPeripheral == null) {
return false;
}
if (btPeripheral.getState() != ConnectionState.DISCONNECTED) {
disconnect();
}
if (callbackBtHandler == null) {
registerCallbackHandler(uiHandler);
}
connect(btPeripheral.getAddress());
return true;
}
private void resetDisconnectTimer() {
disconnectHandler.removeCallbacksAndMessages(null);
disconnectWithDelay();

View File

@@ -42,14 +42,6 @@ public class BluetoothFactory {
|| name.equals("BF700".toLowerCase(Locale.US))) {
return new BluetoothBeurerSanitas(context, BluetoothBeurerSanitas.DeviceType.BEURER_BF710);
}
/*if (name.startsWith("BEURER BF600".toLowerCase(Locale.US))
|| name.startsWith("BEURER BF850".toLowerCase(Locale.US))
|| name.startsWith("BF600".toLowerCase(Locale.US))
|| name.startsWith("BF850".toLowerCase(Locale.US))
|| name.startsWith("BF-600".toLowerCase(Locale.US))
|| name.startsWith("BF-850".toLowerCase(Locale.US))) {
return new BluetoothStandardWeightProfile(context);
}*/
if (name.equals("openScale".toLowerCase(Locale.US))) {
return new BluetoothCustomOpenScale(context);
}
@@ -90,6 +82,10 @@ public class BluetoothFactory {
if (name.equals("Health Scale".toLowerCase(Locale.US))) {
return new BluetoothOneByone(context);
}
if(name.equals("1byone scale".toLowerCase(Locale.US))){
return new BluetoothOneByoneNew(context);
}
if (name.equals("SENSSUN FAT".toLowerCase(Locale.US))) {
return new BluetoothSenssun(context);
}
@@ -116,6 +112,30 @@ public class BluetoothFactory {
if (deviceName.startsWith("Shape200") || deviceName.startsWith("Shape100") || deviceName.startsWith("Shape50") || deviceName.startsWith("Style100")) {
return new BluetoothSoehnle(context);
}
if (deviceName.equals("Hoffen BS-8107")) {
return new BluetoothHoffenBBS8107(context);
}
if (deviceName.equals("ADV") || deviceName.equals("Chipsea-BLE")) {
return new BluetoothOKOK(context);
}
if (deviceName.equals("BF105") || deviceName.equals("BF720")) {
return new BluetoothBeurerBF105(context);
}
if (deviceName.equals("BF600") || deviceName.equals("BF850")) {
return new BluetoothBeurerBF600(context, deviceName);
}
if (deviceName.equals("SBF77") || deviceName.equals("SBF76") || deviceName.equals("BF950")) {
return new BluetoothBeurerBF950(context, deviceName);
}
if (deviceName.equals("SBF72") || deviceName.equals("BF915")) {
return new BluetoothSanitasSBF72(context, deviceName);
}
if (deviceName.equals("Weight Scale")){
return new BluetoothSinocare(context);
}
if (deviceName.equals("CH100")){
return new BluetoothHuaweiAH100(context);
}
return null;
}
}

View File

@@ -93,6 +93,7 @@ public class BluetoothGattUuid {
public static final UUID CHARACTERISTIC_CHANGE_INCREMENT = fromShortCode(0x2a99);
public static final UUID CHARACTERISTIC_USER_CONTROL_POINT = fromShortCode(0x2A9F);
public static final UUID CHARACTERISTIC_USER_AGE = fromShortCode(0x2A80);
public static final UUID CHARACTERISTIC_USER_DATE_OF_BIRTH = fromShortCode(0x2A85);
public static final UUID CHARACTERISTIC_USER_GENDER = fromShortCode(0x2A8C);
public static final UUID CHARACTERISTIC_USER_HEIGHT = fromShortCode(0x2A8E);

View File

@@ -0,0 +1,217 @@
/* Copyright (C) 2021 Karol Werner <karol@ppkt.eu>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.health.openscale.core.bluetooth;
import android.content.Context;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothHoffenBBS8107 extends BluetoothCommunication {
private static final UUID UUID_SERVICE = BluetoothGattUuid.fromShortCode(0xffb0);
private static final UUID UUID_CHARACTERISTIC = BluetoothGattUuid.fromShortCode(0xffb2);
private static final byte MAGIC_BYTE = (byte) 0xFA;
private static final byte RESPONSE_INTERMEDIATE_MEASUREMENT = (byte) 0x01;
private static final byte RESPONSE_FINAL_MEASUREMENT = (byte) 0x02;
private static final byte RESPONSE_ACK = (byte) 0x03;
private static final byte CMD_MEASUREMENT_DONE = (byte) 0x82;
private static final byte CMD_CHANGE_SCALE_UNIT = (byte) 0x83;
private static final byte CMD_SEND_USER_DATA = (byte) 0x85;
private ScaleUser user;
public BluetoothHoffenBBS8107(Context context) {
super(context);
}
@Override
public String driverName() {
return "Hoffen BBS-8107";
}
@Override
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
setNotificationOn(UUID_SERVICE, UUID_CHARACTERISTIC);
user = OpenScale.getInstance().getSelectedScaleUser();
break;
case 1:
// Send user data to the scale
byte[] userData = {
(byte) 0x00, // "plan" id?
user.getGender().isMale() ? (byte) 0x01 : (byte) 0x00,
(byte) user.getAge(),
(byte) user.getBodyHeight(),
};
sendPacket(CMD_SEND_USER_DATA, userData);
// Wait for scale response for this packet
stopMachineState();
break;
case 2:
// Send preferred scale unit to the scale
byte[] weightUnitData = {
(byte) (0x01 + user.getScaleUnit().toInt()),
(byte) 0x00, // always empty
};
sendPacket(CMD_CHANGE_SCALE_UNIT, weightUnitData);
// Wait for scale response for this packet
stopMachineState();
break;
case 3:
// Start measurement
sendMessage(R.string.info_step_on_scale, 0);
// Wait until measurement is done
stopMachineState();
break;
case 4:
// Indicate successful measurement to the scale
byte[] terminateData = {
(byte) 0x00, // always empty
};
sendPacket(CMD_MEASUREMENT_DONE, terminateData);
// Wait for scale response for this packet
stopMachineState();
break;
case 5:
// Terminate the connection - scale will turn itself down after couple seconds
disconnect();
break;
default:
return false;
}
return true;
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
if (value == null || value.length < 2) {
return;
}
if (!verifyData(value) && ((value[0] != MAGIC_BYTE) || (value[1] != RESPONSE_FINAL_MEASUREMENT))) {
// For packet starting with 0xFA 0x02 checksum will be sent in next notify message so we
// will disable checking checksum for this particular packet
Timber.e("Checksum incorrect");
return;
}
if (value[0] != MAGIC_BYTE) {
Timber.w("Received unexpected, but correct data: %s", Arrays.toString(value));
return;
}
float weight;
switch (value[1]) {
case RESPONSE_INTERMEDIATE_MEASUREMENT:
// Got intermediate result
weight = Converters.fromUnsignedInt16Le(value, 4) / 10.0f;
Timber.d("Got intermediate weight: %.1f %s", weight, user.getScaleUnit().toString());
break;
case RESPONSE_FINAL_MEASUREMENT:
// Got final result
addScaleMeasurement(parseFinalMeasurement(value));
resumeMachineState();
break;
case RESPONSE_ACK:
// Got response from scale
Timber.d("Got ack from scale, can proceed");
resumeMachineState();
break;
default:
Timber.e("Got unexpected response: %x", value[1]);
}
}
private ScaleMeasurement parseFinalMeasurement(byte[] value) {
float weight = Converters.fromUnsignedInt16Le(value, 3) / 10.0f;
Timber.d("Got final weight: %.1f %s", weight, user.getScaleUnit().toString());
sendMessage(R.string.info_measuring, weight);
if (user.getScaleUnit() != Converters.WeightUnit.KG) {
// For lb and st this scale will always return result in lb
weight = Converters.toKilogram(weight, Converters.WeightUnit.LB);
}
ScaleMeasurement measurement = new ScaleMeasurement();
measurement.setDateTime(new Date());
measurement.setWeight(weight);
if (value[5] == (byte) 0x00) {
// If user stands bare foot on weight scale it will report more data
measurement.setFat(Converters.fromUnsignedInt16Le(value, 6) / 10.0f);
measurement.setWater(Converters.fromUnsignedInt16Le(value, 8) / 10.0f);
measurement.setMuscle(Converters.fromUnsignedInt16Le(value, 10) / 10.0f);
// Basal metabolic rate is not stored because it's calculated by app
// Bone weight seems to be always returned in kg
measurement.setBone(value[14] / 10.0f);
// BMI is not stored because it's calculated by app
measurement.setVisceralFat(Converters.fromUnsignedInt16Le(value, 17) / 10.0f);
// Internal body age is not stored in app
} else if (value[5] == (byte) 0x04) {
Timber.w("No more data to store");
} else {
Timber.e("Received unexpected value: %x", value[5]);
}
return measurement;
}
private void sendPacket(byte command, byte[] payload) {
// Add required fields to provided payload and send the packet
byte[] outputArray = new byte[payload.length + 4];
outputArray[0] = MAGIC_BYTE;
outputArray[1] = command;
outputArray[2] = (byte) payload.length;
System.arraycopy(payload, 0, outputArray, 3, payload.length);
// Calculate checksum skipping first element
outputArray[outputArray.length - 1] = xorChecksum(outputArray, 1, outputArray.length - 2);
writeBytes(UUID_SERVICE, UUID_CHARACTERISTIC, outputArray, true);
}
private boolean verifyData(byte[] data) {
// First byte is skipped in calculated checksum
return xorChecksum(data, 1, data.length - 1) == 0;
}
}

View File

@@ -0,0 +1,805 @@
/* Copyright (C) 2014 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.health.openscale.core.bluetooth;
import android.content.Context;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
import timber.log.Timber;
// +++
import android.os.Handler;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class BluetoothHuaweiAH100 extends BluetoothCommunication {
private static final UUID SERVICE_AH100_CUSTOM_SERVICE = BluetoothGattUuid.fromShortCode(0xfaa0);
private static final UUID SERVICE_AH100_CUSTOM_SEND = BluetoothGattUuid.fromShortCode(0xfaa1);
private static final UUID SERVICE_AH100_CUSTOM_RECEIVE = BluetoothGattUuid.fromShortCode(0xfaa2);
// +++
private static byte[] user_id = {0, 0, 0, 0, 0, 0, 0};
private enum STEPS {
INIT,
INIT_W,
AUTHORISE,
SCALE_UNIT,
SCALE_TIME,
USER_INFO,
SCALE_VERSION,
WAIT_MEASUREMENT,
READ_HIST,
READ_HIST_NEXT,
EXIT,
BIND,
EXIT2
}
private static final byte AH100_NOTIFICATION_WAKEUP = 0x00;
private static final byte AH100_NOTIFICATION_GO_SLEEP = 0x01;
private static final byte AH100_NOTIFICATION_UNITS_SET = 0x02;
private static final byte AH100_NOTIFICATION_REMINDER_SET = 0x03;
private static final byte AH100_NOTIFICATION_SCALE_CLOCK = 0x08;
private static final byte AH100_NOTIFICATION_SCALE_VERSION = 0x0C;
private static final byte AH100_NOTIFICATION_MEASUREMENT = 0x0E;
private static final byte AH100_NOTIFICATION_MEASUREMENT2 = (byte) 0x8E;
private static final byte AH100_NOTIFICATION_MEASUREMENT_WEIGHT = 0x0F;
private static final byte AH100_NOTIFICATION_HISTORY_RECORD = 0x10;
private static final byte AH100_NOTIFICATION_HISTORY_RECORD2 = (byte) 0x90;
private static final byte AH100_NOTIFICATION_UPGRADE_RESPONSE = 0x11;
private static final byte AH100_NOTIFICATION_UPGRADE_RESULT = 0x12;
private static final byte AH100_NOTIFICATION_WEIGHT_OVERLOAD = 0x13;
private static final byte AH100_NOTIFICATION_LOW_POWER = 0x14;
private static final byte AH100_NOTIFICATION_MEASUREMENT_ERROR = 0x15;
private static final byte AH100_NOTIFICATION_SET_CLOCK_ACK = 0x16;
private static final byte AH100_NOTIFICATION_OTA_UPGRADE_READY = 0x17;
private static final byte AH100_NOTIFICATION_SCALE_MAC_RECEIVED = 0x18;
private static final byte AH100_NOTIFICATION_HISTORY_UPLOAD_DONE = 0x19;
private static final byte AH100_NOTIFICATION_USER_CHANGED = 0x20;
private static final byte AH100_NOTIFICATION_AUTHENTICATION_RESULT = 0x26;
private static final byte AH100_NOTIFICATION_BINDING_SUCCESSFUL = 0x27;
private static final byte AH100_NOTIFICATION_FIRMWARE_UPDATE_RECEIVED = 0x28;
private static final byte AH100_CMD_SET_UNIT = 2;
private static final byte AH100_CMD_DELETE_ALARM_CLOCK = 3;
private static final byte AH100_CMD_SET_ALARM_CLOCK = 4;
private static final byte AH100_CMD_DELETE_ALL_ALARM_CLOCK = 5;
private static final byte AH100_CMD_GET_ALARM_CLOCK_BY_NO = 6;
private static final byte AH100_CMD_SET_SCALE_CLOCK = 8;
private static final byte AH100_CMD_SELECT_USER = 10;
private static final byte AH100_CMD_USER_INFO = 9;
private static final byte AH100_CMD_GET_RECORD = 11;
private static final byte AH100_CMD_GET_VERSION = 12;
private static final byte AH100_CMD_GET_SCALE_CLOCK = 14;
private static final byte AH100_CMD_GET_USER_LIST_MARK = 15;
private static final byte AH100_CMD_UPDATE_SIGN = 16;
private static final byte AH100_CMD_DELETE_ALL_USER = 17;
private static final byte AH100_CMD_SET_BLE_BROADCAST_TIME = 18;
private static final byte AH100_CMD_FAT_RESULT_ACK = 19;
private static final byte AH100_CMD_GET_LAST_RECORD = 20;
private static final byte AH100_CMD_DISCONNECT_BT = 22;
private static final byte AH100_CMD_HEART_BEAT = 32;
private static final byte AH100_CMD_AUTH = 36;
private static final byte AH100_CMD_BIND_USER = 37;
private static final byte AH100_CMD_OTA_PACKAGE = (byte) 0xDD;
private Context context;
private byte[] authCode;
private byte[] initialKey ;
private byte[] initialValue ;
private byte[] magicKey ;
private int triesToAuth = 0;
private int triesToBind = 0;
private int lastMeasuredWeight = -1;
private boolean authorised = false;
private boolean scaleWakedUp = false;
private boolean scaleBinded = false;
private byte receivedPacketType = 0x00;
private byte[] receivedPacket1;
private Handler beatHandler;
public BluetoothHuaweiAH100(Context context) {
super(context);
this.context = context;
this.beatHandler = new Handler();
authCode = getUserID();
initialKey = hexToByteArray("3D A2 78 4A FB 87 B1 2A 98 0F DE 34 56 73 21 56");
initialValue = hexToByteArray("4E F7 64 32 2F DA 76 32 12 3D EB 87 90 FE A2 19");
}
@Override
public String driverName() {
return "Huawei AH100 Body Fat Scale";
}
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
@Override
protected boolean onNextStep(int stepNr) {
STEPS step;
try {
step = STEPS.values()[stepNr];
} catch (Exception e) {
// stepNr is bigger then we have in STEPS
return false;
}
switch (step) {
case INIT:
// wait scale wake up
Timber.d("AH100::onNextStep step 0 = set notification");
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
// Setup notification
setNotificationOn(SERVICE_AH100_CUSTOM_SERVICE, SERVICE_AH100_CUSTOM_RECEIVE);
triesToAuth = 0;
authorised = false;
stopMachineState();
break;
case INIT_W:
stopMachineState();
break;
case AUTHORISE:
if ( scaleWakedUp == false ) {
jumpNextToStepNr( STEPS.INIT.ordinal() );
break;
}
// authorize in scale
Timber.d("AH100::onNextStep = authorize on scale");
triesToAuth++;
AHcmdAutorise();
stopMachineState();
break;
case SCALE_UNIT:
Timber.d("AH100::onNextStep = set scale unit");
AHcmdSetUnit();
stopMachineState();
break;
case SCALE_TIME:
Timber.d("AH100::onNextStep = set scale time");
AHcmdDate();
stopMachineState();
break;
case USER_INFO:
Timber.d("AH100::onNextStep = send user info to scale");
if ( !authorised ) {
jumpNextToStepNr( STEPS.AUTHORISE.ordinal() );
break;
}
// set user data
AHcmdUserInfo();
stopMachineState();
break;
case SCALE_VERSION:
Timber.d("AH100::onNextStep = request scale version");
if ( !authorised ) {
jumpNextToStepNr( STEPS.AUTHORISE.ordinal() );
break;
}
AHcmdGetVersion();
stopMachineState();
break;
case WAIT_MEASUREMENT:
AHcmdGetUserList();
Timber.d("AH100::onNextStep = Do nothing, wait while scale tries disconnect");
sendMessage(R.string.info_step_on_scale, 0);
stopMachineState();
break;
case READ_HIST:
Timber.d("AH100::onNextStep = read history record from scale");
if ( !authorised ) {
jumpNextToStepNr( STEPS.AUTHORISE.ordinal() );
break;
}
AHcmdReadHistory();
stopMachineState();
break;
case READ_HIST_NEXT:
Timber.d("AH100::onNextStep = read NEXT history record from scale");
if ( !authorised ) {
jumpNextToStepNr( STEPS.AUTHORISE.ordinal() );
break;
}
AHcmdReadHistoryNext();
stopMachineState();
break;
case EXIT:
Timber.d("AH100::onNextStep = Exit");
authorised = false;
scaleWakedUp = false;
stopHeartBeat();
disconnect();
return false;
case BIND:
Timber.d("AH100::onNextStep = BIND scale to OpenScale");
// Start measurement
sendMessage(R.string.info_step_on_scale, 0);
triesToBind++;
AHcmdBind();
AHcmdBind();
stopMachineState();
break;
case EXIT2:
authorised = false;
scaleWakedUp = false;
stopHeartBeat();
disconnect();
Timber.d("AH100::onNextStep = BIND Exit");
default:
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
final byte[] data = value;
byte cmdlength = 0;
Timber.d("AH100::onBluetoothNotify uuid: %s", characteristic.toString());
if (data != null && data.length > 2) {
Timber.d("===> New NOTIFY hex data: %s", byteInHex(data));
cmdlength = data[1];
// responce from scale received
switch (data[2]) {
case AH100_NOTIFICATION_WAKEUP:
scaleWakedUp = true;
if (getStepNr() - 1 == STEPS.INIT_W.ordinal() ) {
Timber.d("AH100::onNotify = Scale is waked up in Init-stage");
startHeartBeat();
resumeMachineState();
break;
}
// if (getStepNr() - 1 == STEPS.BIND.ordinal() ) {
// Timber.d("AH100::onNotify = Scale is waked up in Init-stage");
// jumpBackOneStep();
// resumeMachineState();
// break;
// }
Timber.d("AH100::onNotify = Scale is waked up");
// authorised = false;
// jumpNextToStepNr(STEPS.AUTHORISE.ordinal());
// resumeMachineState();
break;
case AH100_NOTIFICATION_GO_SLEEP:
resumeMachineState();
break;
case AH100_NOTIFICATION_UNITS_SET:
resumeMachineState();
break;
case AH100_NOTIFICATION_REMINDER_SET:
break;
case AH100_NOTIFICATION_SCALE_CLOCK:
resumeMachineState();
break;
case AH100_NOTIFICATION_SCALE_VERSION:
byte[] VERpayload = getPayload(data);
Timber.d("Get Scale Version: input data: %s", byteInHex(VERpayload));
resumeMachineState();
break;
case AH100_NOTIFICATION_MEASUREMENT:
if (data[0] == (byte) 0xBD) {
Timber.d("Scale plain response received");
}
if (data[0] == (byte) 0xBC) {
Timber.d("Scale encoded response received");
receivedPacket1 = Arrays.copyOfRange(data, 0, data.length );
receivedPacketType = AH100_NOTIFICATION_MEASUREMENT;
}
break;
case AH100_NOTIFICATION_MEASUREMENT2:
if (data[0] == (byte) 0xBC) { /// normal packet
Timber.d("Scale encoded response received");
if (receivedPacketType == AH100_NOTIFICATION_MEASUREMENT) {
AHrcvEncodedMeasurement(receivedPacket1, data, AH100_NOTIFICATION_MEASUREMENT);
receivedPacketType = 0x00;
if (scaleBinded == true) {
AHcmdMeasurementAck();
} else {
if (lastMeasuredWeight > 0) {
AHcmdUserInfo(lastMeasuredWeight);
}
}
jumpNextToStepNr( STEPS.READ_HIST.ordinal() );
resumeMachineState();
}
break;
}
if (data[0] == (byte) 0xBD) {
Timber.d("Scale plain response received");
}
jumpNextToStepNr( STEPS.INIT.ordinal() );
resumeMachineState();
break;
case AH100_NOTIFICATION_MEASUREMENT_WEIGHT:
break;
case AH100_NOTIFICATION_HISTORY_RECORD:
if (data[0] == (byte) 0xBD) {
Timber.d("Scale plain response received");
}
if (data[0] == (byte) 0xBC) {
Timber.d("Scale encoded response received");
receivedPacket1 = Arrays.copyOfRange(data, 0, data.length );
receivedPacketType = AH100_NOTIFICATION_HISTORY_RECORD;
}
break;
case AH100_NOTIFICATION_HISTORY_RECORD2:
if (data[0] == (byte) 0xBC) { /// normal packet
Timber.d("Scale encoded response received");
if (receivedPacketType == AH100_NOTIFICATION_HISTORY_RECORD) {
AHrcvEncodedMeasurement(receivedPacket1, data, AH100_NOTIFICATION_HISTORY_RECORD);
receivedPacketType = 0x00;
// todo: jumpback only in ReadHistoryNext
jumpNextToStepNr(STEPS.READ_HIST_NEXT.ordinal(),
STEPS.READ_HIST_NEXT.ordinal());
resumeMachineState();
}
break;
}
if (data[0] == (byte) 0xBD) {
Timber.d("Scale plain response received");
}
jumpNextToStepNr( STEPS.INIT.ordinal() );
resumeMachineState();
break;
case AH100_NOTIFICATION_UPGRADE_RESPONSE:
break;
case AH100_NOTIFICATION_UPGRADE_RESULT:
break;
case AH100_NOTIFICATION_WEIGHT_OVERLOAD:
break;
case AH100_NOTIFICATION_LOW_POWER:
break;
case AH100_NOTIFICATION_MEASUREMENT_ERROR:
break;
case AH100_NOTIFICATION_SET_CLOCK_ACK:
break;
case AH100_NOTIFICATION_OTA_UPGRADE_READY:
break;
case AH100_NOTIFICATION_SCALE_MAC_RECEIVED:
break;
case AH100_NOTIFICATION_HISTORY_UPLOAD_DONE:
resumeMachineState();
break;
case AH100_NOTIFICATION_USER_CHANGED:
resumeMachineState(STEPS.USER_INFO.ordinal()); // waiting wake up in state 4
break;
case AH100_NOTIFICATION_AUTHENTICATION_RESULT:
byte[] ARpayload = getPayload(data);
if ( 1 == ARpayload[0] ){
authorised = true;
magicKey = hexConcatenate(obfuscate(authCode) , Arrays.copyOfRange(initialKey, 7, initialKey.length ) );
resumeMachineState(STEPS.AUTHORISE.ordinal()); // waiting wake up in state 4
} else {
if (triesToAuth < 3){ // try again
jumpNextToStepNr(STEPS.AUTHORISE.ordinal());
} else { // bind scale to own code
jumpNextToStepNr(STEPS.BIND.ordinal());
}
resumeMachineState();
}
// acknowledge that you received the last history data
break;
case AH100_NOTIFICATION_BINDING_SUCCESSFUL:
// jump to authorise again
jumpNextToStepNr(STEPS.SCALE_TIME.ordinal());
scaleBinded = true;
// TODO: count binding tries
break;
case AH100_NOTIFICATION_FIRMWARE_UPDATE_RECEIVED:
break;
default:
break;
} // switch command
}
}
private void AHcmdHeartBeat() {
AHsendCommand(AH100_CMD_HEART_BEAT, new byte[0] );
}
private void AHcmdAutorise() {
AHsendCommand(AH100_CMD_AUTH, authCode);
}
private void AHcmdBind() {
AHsendCommand(AH100_CMD_BIND_USER, authCode);
}
private void AHcmdDate() {
/*
payload[0]: lowerByte(year)
payload[1]: upperByte(year)
payload[2]: month (1..12)
payload[3]: dayOfMonth
payload[4]: hourOfDay (0-23)
payload[5]: minute
payload[6]: second
payload[7]: day of week (Monday=1, Sunday=7)
*/
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 dow = (byte)currentDateTime.get(Calendar.DAY_OF_WEEK);
byte[] date = new byte[]{
0x00, 0x00, // year, fill later
month,
day,
hour,
min,
sec,
dow
};
Converters.toInt16Le(date, 0, year);
Timber.d("AH100::AHcmdDate: data to send: %s", byteInHex(date) );
AHsendCommand(AH100_CMD_SET_SCALE_CLOCK, date);
}
private void AHcmdUserInfo() {
///String user example = "27 af 00 2a 03 ff ff";
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
int weight = (int) currentUser.getInitialWeight() * 10;
AHcmdUserInfo(weight);
}
private void AHcmdUserInfo(int weight) {
///String user example = "27 af 00 2a 03 ff ff";
/*
payload[7] = sex == 1 ? age | 0x80 : age
payload[8] = height of the user
payload[9] = 0
payload[10] = lowerByte(weight)
payload[11] = upperByte(weight)
payload[12] = lowerByte(impedance)
payload[13] = upperByte(impedance)
*/
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
byte height = (byte) currentUser.getBodyHeight();
byte sex = currentUser.getGender().isMale() ? 0 : (byte) 0x80;
byte age = (byte) ( sex | ((byte) currentUser.getAge()) );
byte[] user = new byte[]{
age,
height,
0,
0x00, 0x00, // weight, fill later
(byte) 0xFF, (byte) 0xFF, // resistance, wkwtfdim
(byte) 0x1C, (byte) 0xE2,
};
Converters.toInt16Le(user, 3, weight);
byte[] userinfo = hexConcatenate( authCode, user );
AHsendCommand(AH100_CMD_USER_INFO, userinfo, 14);
}
private void AHcmdReadHistory() {
byte[] pl;
byte[] xp = {xorChecksum(authCode, 0, authCode.length)};
pl = hexConcatenate( authCode, xp );
AHsendCommand(AH100_CMD_GET_RECORD, pl, 0x07 - 1);
}
private void AHcmdReadHistoryNext() {
byte[] pl = {0x01};
AHsendCommand(AH100_CMD_GET_RECORD, pl);
}
private void AHcmdSetUnit() {
// TODO: set correct units
byte[] pl = new byte[]{0x01}; // 1 = kg; 2 = pounds. set kg only
AHsendCommand(AH100_CMD_SET_UNIT, pl);
}
private void AHcmdGetUserList() {
//byte[] pl = new byte[]{};
// byte[] pl = authCode;
// AHsendCommand(AH100_CMD_SELECT_USER, pl);
}
private void AHcmdGetVersion() {
byte[] pl = new byte[]{};
AHsendCommand(AH100_CMD_GET_VERSION, pl);
}
private void AHcmdMeasurementAck() {
byte[] pl = new byte[]{0x00};
AHsendCommand(AH100_CMD_FAT_RESULT_ACK, pl);
}
private void AHrcvEncodedMeasurement(byte[] encdata, byte[] encdata2, byte type) {
byte[] payload = getPayload(encdata);
byte[] data;
try{
data = decryptAES(payload, magicKey, initialValue);
Timber.d("Decrypted measurement: hex data: %s", byteInHex(data));
if ( (type == AH100_NOTIFICATION_MEASUREMENT) ||
(type == AH100_NOTIFICATION_HISTORY_RECORD) ) {
AHaddFatMeasurement(data);
}
} catch (Exception e) {
Timber.d("Decrypting FAIL!!!");
}
}
private void AHaddFatMeasurement(byte[] data) {
if (data.length < 14) {
Timber.d(":: AHaddFatMeasurement : data is too short. Expected at least 14 bytes of data." );
return ;
}
byte userid = data[0]; ///// Arrays.copyOfRange(data, 0, 0 );
lastMeasuredWeight = Converters.fromUnsignedInt16Le(data, 1);
float weight = lastMeasuredWeight / 10.0f;
float fat = Converters.fromUnsignedInt16Le(data, 3) / 10.0f;
int year = Converters.fromUnsignedInt16Le(data, 5) ;
int resistance = Converters.fromUnsignedInt16Le(data, 13) ;
byte month = (byte) (data[7] - 1); // 1..12 to zero-based month
byte dayInMonth = data[8];
byte hour = data[9];
byte minute = data[10];
byte second = data[11];
byte weekNumber = data[12];
Timber.d("---- measured userid %d",userid );
Timber.d("---- measured weight %f",weight );
Timber.d("---- measured fat %f",fat );
Timber.d("---- measured resistance %d",resistance );
Timber.d("---- measured year %d",year );
Timber.d("---- measured month %d",month );
Timber.d("---- measured dayInMonth %d",dayInMonth );
Timber.d("---- measured hour %d",hour );
Timber.d("---- measured minute %d",minute );
Timber.d("---- measured second %d",second );
Timber.d("---- measured week day %d",weekNumber );
///////////////////////////
Calendar calendar = Calendar.getInstance();
calendar.set( year, month, dayInMonth, hour, minute, second);
Date date = calendar.getTime();
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
ScaleMeasurement receivedMeasurement = new ScaleMeasurement();
receivedMeasurement.setUserId(currentUser.getId());
receivedMeasurement.setDateTime( date );
receivedMeasurement.setWeight(weight);
receivedMeasurement.setFat(fat);
// receivedMeasurement.setWater(water);
// receivedMeasurement.setMuscle(muscle);
// receivedMeasurement.setBone(bone);
// todo: calculate water, muscle, bones
addScaleMeasurement(receivedMeasurement);
}
private void startHeartBeat() {
Timber.d("*** Heart beat started");
beatHandler.postDelayed(new Runnable() {
@Override
public void run() {
Timber.d("*** heart beat.");
AHcmdHeartBeat();
}
}, 2000); // 2 s
}
private void resetHeartBeat() {
Timber.d("*** 0 heart beat reset");
beatHandler.removeCallbacksAndMessages(null);
startHeartBeat();
}
private void stopHeartBeat() {
Timber.d("*** ! heart beat stopped");
beatHandler.removeCallbacksAndMessages(null);
}
private void AHsendCommand(byte cmd, byte[] payload ) {
AHsendCommand(cmd, payload, payload.length );
}
private void AHsendCommand(byte cmd, byte[] payload, int len ) {
resetHeartBeat();
if ( (cmd == AH100_CMD_USER_INFO) ) {
AHsendEncryptedCommand(cmd, payload, len);
return;
}
byte[] packet ;
byte[] header;
header = new byte[]{(byte) (0xDB),
(byte) (len + 1),
cmd};
packet = hexConcatenate( header, obfuscate(payload) );
try {
writeBytes(SERVICE_AH100_CUSTOM_SERVICE,
SERVICE_AH100_CUSTOM_SEND,
packet);
} catch (Exception e) {
Timber.d("AHsendCommand: CANNOT WRITE COMMAND");
stopHeartBeat();
}
}
private void AHsendEncryptedCommand(byte cmd, byte[] payload , int len ) {
byte[] packet ;
byte[] header;
byte[] encrypted;
Timber.d("AHsendEncryptedCommand: input data: %s", byteInHex(payload));
encrypted = encryptAES(payload, magicKey, initialValue); //encryptAES
header = new byte[]{(byte) (0xDC),
(byte) (len + 0 ),
cmd};
packet = hexConcatenate( header, obfuscate(encrypted) );
try {
writeBytes(SERVICE_AH100_CUSTOM_SERVICE,
SERVICE_AH100_CUSTOM_SEND,
packet);
} catch (Exception e) {
Timber.d("AHsendEncryptedCommand: CANNOT WRITE COMMAND");
stopHeartBeat();
}
}
public byte[] getUserID() {
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
byte id = (byte) currentUser.getId();
byte[] auth = new byte[] {0x11, 0x22, 0x33, 0x44, 0x55, 0x00, id};
auth[5] = xorChecksum(auth, 0, auth.length); // set xor of authorise code to 0x00
return auth;
///// return getfakeUserID();
}
public byte[] getfakeUserID() {
String fid = "0f 00 43 06 7b 4e 7f"; // "c7b25de6bed0b7";
byte[] auth = hexToByteArray(fid) ;
return auth;
}
public byte[] encryptAES(byte[] data, byte[] key, byte[] ivs) {
Timber.d("Encoding : input hex data: %s", byteInHex(data));
Timber.d("Encoding : encoding key : %s", byteInHex(key));
Timber.d("Encoding : initial value : %s", byteInHex(ivs));
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public byte[] decryptAES(byte[] data, byte[] key, byte[] ivs) {
Timber.d("Decoding : input hex data: %s", byteInHex(data));
Timber.d("Decoding : encoding key : %s", byteInHex(key));
Timber.d("Decoding : initial value : %s", byteInHex(ivs));
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivps);
byte[] ret = cipher.doFinal(data);
Timber.d("### decryptAES : hex data: %s", byteInHex(ret));
return ret;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public byte[] hexToByteArray(String hexStr) {
String hex = hexStr.replaceAll (" ","").replaceAll (":","");
hex = hex.length()%2 != 0?"0"+hex:hex;
byte[] b = new byte[hex.length() / 2];
for (int i = 0; i < b.length; i++) {
int index = i * 2;
int v = Integer.parseInt(hex.substring(index, index + 2), 16);
b[i] = (byte) v;
}
return b;
}
public byte[] hexConcatenate(byte[] A, byte[] B) {
byte[] C = new byte[A.length + B.length];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
try {
outputStream.write( A );
outputStream.write( B );
C = outputStream.toByteArray( );
} catch (IOException e) {
e.printStackTrace();
}
return C;
}
public byte[] getPayload(byte[] data) {
byte[] obfpayload = Arrays.copyOfRange(data, 3, data.length );
byte[] payload = obfuscate(obfpayload);
Timber.d("Deobfuscated payload: %s", byteInHex(payload));
return payload;
}
private byte[] obfuscate(byte[] rawdata) {
final byte[] data = Arrays.copyOfRange(rawdata, 0, rawdata.length );
final byte[] MAC;
MAC = getScaleMacAddress();
Timber.d("Obfuscation: input hex data: %s", byteInHex(data));
//Timber.d("Obfuscation: MAC hex data: %s", byteInHex(MAC));
byte m = 0 ;
for(int l=0; l< data.length; l++,m++){
if (MAC.length <= m) { m = 0; }
data[l] ^= MAC[m];
}
//Timber.d("Obfuscation: out hex data: %s", byteInHex(data));
return data;
}
}

View File

@@ -16,6 +16,8 @@
package com.health.openscale.core.bluetooth;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS.UNEXPECTED_ERROR;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
@@ -28,7 +30,6 @@ import com.health.openscale.core.utils.Converters;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
@@ -36,8 +37,6 @@ import java.util.UUID;
import timber.log.Timber;
import static com.health.openscale.core.bluetooth.BluetoothCommunication.BT_STATUS.UNEXPECTED_ERROR;
public class BluetoothMiScale2 extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_HISTORY_CHARACTERISTIC = UUID.fromString("00002a2f-0000-3512-2118-0009af100700");
@@ -74,13 +73,6 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
resumeMachineState();
}
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);
}
@@ -141,13 +133,12 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
final byte ctrlByte1 = data[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);
final boolean isImpedance = isBitSet(ctrlByte1, 1);
if (isStabilized && !isWeightRemoved && !isDateInvalid) {
if (isStabilized && !isWeightRemoved) {
final int year = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
final int month = (int) data[4];
@@ -195,7 +186,8 @@ public class BluetoothMiScale2 extends BluetoothCommunication {
scaleBtData.setWater(miScaleLib.getWater(weight, impedance));
scaleBtData.setVisceralFat(miScaleLib.getVisceralFat(weight));
scaleBtData.setFat(miScaleLib.getBodyFat(weight, impedance));
scaleBtData.setMuscle((100.0f / scaleBtData.getWeight()) * miScaleLib.getMuscle(weight, impedance)); // convert muscle in kg to percent
scaleBtData.setMuscle((100.0f / weight) * miScaleLib.getMuscle(weight, impedance)); // convert muscle in kg to percent
scaleBtData.setLbm(miScaleLib.getLBM(weight, impedance));
scaleBtData.setBone(miScaleLib.getBoneMass(weight, impedance));
} else {
Timber.d("Impedance value is zero");

View File

@@ -0,0 +1,164 @@
package com.health.openscale.core.bluetooth;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.welie.blessed.BluetoothCentralManager;
import com.welie.blessed.BluetoothCentralManagerCallback;
import com.welie.blessed.BluetoothPeripheral;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
import timber.log.Timber;
public class BluetoothOKOK extends BluetoothCommunication {
private static final int MANUFACTURER_DATA_ID_V20 = 0x20ca; // 16-bit little endian "header" 0xca 0x20
private static final int MANUFACTURER_DATA_ID_V11 = 0x11ca; // 16-bit little endian "header" 0xca 0x11
private static final int IDX_V20_FINAL = 6;
private static final int IDX_V20_WEIGHT_MSB = 8;
private static final int IDX_V20_WEIGHT_LSB = 9;
private static final int IDX_V20_IMPEDANCE_MSB = 10;
private static final int IDX_V20_IMPEDANCE_LSB = 11;
private static final int IDX_V20_CHECKSUM = 12;
private static final int IDX_V11_WEIGHT_MSB = 3;
private static final int IDX_V11_WEIGHT_LSB = 4;
private static final int IDX_V11_BODY_PROPERTIES = 9;
private static final int IDX_V11_CHECKSUM = 16;
private BluetoothCentralManager central;
private final BluetoothCentralManagerCallback btCallback = new BluetoothCentralManagerCallback() {
@Override
public void onDiscoveredPeripheral(@NotNull BluetoothPeripheral peripheral, @NotNull ScanResult scanResult) {
SparseArray<byte[]> manufacturerSpecificData = scanResult.getScanRecord().getManufacturerSpecificData();
if (manufacturerSpecificData.indexOfKey(MANUFACTURER_DATA_ID_V20) > -1) {
byte[] data = manufacturerSpecificData.get(MANUFACTURER_DATA_ID_V20);
float divider = 10.0f;
byte checksum = 0x20; // Version field is part of the checksum, but not in array
if (data == null || data.length != 19)
return;
if ((data[IDX_V20_FINAL] & 1) == 0)
return;
for (int i = 0; i < IDX_V20_CHECKSUM; i++)
checksum ^= data[i];
if (data[IDX_V20_CHECKSUM] != checksum) {
Timber.d("Checksum error, got %x, expected %x", data[IDX_V20_CHECKSUM] & 0xff, checksum & 0xff);
return;
}
if ((data[IDX_V20_FINAL] & 4) == 4)
divider = 100.0f;
int weight = data[IDX_V20_WEIGHT_MSB] & 0xff;
weight = weight << 8 | (data[IDX_V20_WEIGHT_LSB] & 0xff);
int impedance = data[IDX_V20_IMPEDANCE_MSB] & 0xff;
impedance = impedance << 8 | (data[IDX_V20_IMPEDANCE_LSB] & 0xff);
Timber.d("Got weight: %f and impedance %f", weight / divider, impedance / 10f);
ScaleMeasurement entry = new ScaleMeasurement();
entry.setWeight(weight / divider);
addScaleMeasurement(entry);
disconnect();
} else if (manufacturerSpecificData.indexOfKey(MANUFACTURER_DATA_ID_V11) > -1) {
byte[] data = manufacturerSpecificData.get(MANUFACTURER_DATA_ID_V11);
float divider = 10.0f;
float extraWeight = 0;
byte checksum = (byte)0xca ^ (byte)0x11; // Version and magic fields are part of the checksum, but not in array
if (data == null || data.length != IDX_V11_CHECKSUM + 6 + 1)
return;
for (int i = 0; i < IDX_V11_CHECKSUM; i++)
checksum ^= data[i];
if (data[IDX_V11_CHECKSUM] != checksum) {
Timber.d("Checksum error, got %x, expected %x", data[IDX_V11_CHECKSUM] & 0xff, checksum & 0xff);
return;
}
int weight = data[IDX_V11_WEIGHT_MSB] & 0xff;
weight = weight << 8 | (data[IDX_V11_WEIGHT_LSB] & 0xff);
switch ((data[IDX_V11_BODY_PROPERTIES] >> 1) & 3) {
default:
Timber.w("Invalid weight scale received, assuming 1 decimal");
/* fall-through */
case 0:
divider = 10.0f;
break;
case 1:
divider = 1.0f;
break;
case 2:
divider = 100.0f;
break;
}
switch ((data[IDX_V11_BODY_PROPERTIES] >> 3) & 3) {
case 0: // kg
break;
case 1: // Jin
divider *= 2;
break;
case 3: // st & lb
extraWeight = (weight >> 8) * 6.350293f;
weight &= 0xff;
/* fall-through */
case 2: // lb
divider *= 2.204623;
break;
}
Timber.d("Got weight: %f", weight / divider);
ScaleMeasurement entry = new ScaleMeasurement();
entry.setWeight(extraWeight + weight / divider);
addScaleMeasurement(entry);
disconnect();
}
}
};
public BluetoothOKOK(Context context)
{
super(context);
central = new BluetoothCentralManager(context, btCallback, new Handler(Looper.getMainLooper()));
}
@Override
public String driverName() {
return "OKOK";
}
@Override
public void connect(String macAddress) {
Timber.d("Mac address: %s", macAddress);
List<ScanFilter> filters = new LinkedList<ScanFilter>();
ScanFilter.Builder b = new ScanFilter.Builder();
b.setDeviceAddress(macAddress);
b.setDeviceName("ADV");
b.setManufacturerData(MANUFACTURER_DATA_ID_V20, null, null);
filters.add(b.build());
b.setDeviceName("Chipsea-BLE");
b.setManufacturerData(MANUFACTURER_DATA_ID_V11, null, null);
filters.add(b.build());
central.scanForPeripheralsUsingFilters(filters);
}
@Override
public void disconnect() {
if (central != null)
central.stopScan();
central = null;
super.disconnect();
}
@Override
protected boolean onNextStep(int stepNr) {
return false;
}
}

View File

@@ -145,9 +145,8 @@ public class BluetoothOneByone extends BluetoothCommunication {
private void parseBytes(byte[] weightBytes) {
float weight = Converters.fromUnsignedInt16Le(weightBytes, 3) / 100.0f;
int impedanceCoeff = Converters.fromUnsignedInt24Le(weightBytes, 5);
int impedanceValue = weightBytes[5] + weightBytes[6] + weightBytes[7];
boolean impedancePresent = (weightBytes[9] != 1) && (impedanceCoeff != 0);
float impedanceValue = ((float)(((weightBytes[2] & 0xFF) << 8) + (weightBytes[1] & 0xFF))) * 0.1f;
boolean impedancePresent = (weightBytes[9] != 1) && (impedanceValue != 0);
boolean dateTimePresent = weightBytes.length >= 18;
if (!impedancePresent || (!dateTimePresent && historicMeasurement)) {
@@ -166,7 +165,7 @@ public class BluetoothOneByone extends BluetoothCommunication {
final ScaleUser scaleUser = OpenScale.getInstance().getSelectedScaleUser();
Timber.d("received bytes [%s]", byteInHex(weightBytes));
Timber.d("received decrypted bytes [weight: %.2f, impedanceCoeff: %d, impedanceValue: %d]", weight, impedanceCoeff, impedanceValue);
Timber.d("received decoded bytes [weight: %.2f, impedanceValue: %f]", weight, impedanceValue);
Timber.d("user [%s]", scaleUser);
int sex = 0, peopleType = 0;
@@ -202,29 +201,31 @@ public class BluetoothOneByone extends BluetoothCommunication {
try {
dateTime.setLenient(false);
scaleBtData.setDateTime(dateTime.getTime());
scaleBtData.setFat(oneByoneLib.getBodyFat(weight, impedanceValue));
scaleBtData.setWater(oneByoneLib.getWater(scaleBtData.getFat()));
scaleBtData.setBone(oneByoneLib.getBoneMass(weight, impedanceValue));
scaleBtData.setVisceralFat(oneByoneLib.getVisceralFat(weight));
scaleBtData.setMuscle(oneByoneLib.getMuscle(weight, impedanceValue));
scaleBtData.setLbm(oneByoneLib.getLBM(weight, scaleBtData.getFat()));
Timber.d("scale measurement [%s]", scaleBtData);
if (dateTime.getTimeInMillis() - lastDateTime.getTimeInMillis() < DATE_TIME_THRESHOLD) {
return; // don't save measurements too close to each other
}
lastDateTime = dateTime;
addScaleMeasurement(scaleBtData);
}
catch (IllegalArgumentException e) {
if (historicMeasurement) {
Timber.d("invalid time-stamp: year %d, month %d, day %d, hour %d, minute %d, second %d",
Converters.fromUnsignedInt16Be(weightBytes, 11),
weightBytes[13], weightBytes[14], weightBytes[15],
weightBytes[16], weightBytes[17]);
Converters.fromUnsignedInt16Be(weightBytes, 11),
weightBytes[13], weightBytes[14], weightBytes[15],
weightBytes[16], weightBytes[17]);
return; // discard historic measurement with invalid time-stamp
}
}
scaleBtData.setFat(oneByoneLib.getBodyFat(weight, impedanceCoeff));
scaleBtData.setWater(oneByoneLib.getWater(scaleBtData.getFat()));
scaleBtData.setBone(oneByoneLib.getBoneMass(weight, impedanceValue));
scaleBtData.setVisceralFat(oneByoneLib.getVisceralFat(weight));
scaleBtData.setMuscle(oneByoneLib.getMuscle(weight, scaleBtData.getFat(), scaleBtData.getBone()));
Timber.d("scale measurement [%s]", scaleBtData);
if (dateTime.getTimeInMillis() - lastDateTime.getTimeInMillis() < DATE_TIME_THRESHOLD) {
return; // don't save measurements too close to each other
}
lastDateTime = dateTime;
addScaleMeasurement(scaleBtData);
}
}

View File

@@ -0,0 +1,333 @@
package com.health.openscale.core.bluetooth;
import android.content.Context;
import androidx.annotation.NonNull;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.bluetooth.lib.OneByoneNewLib;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothOneByoneNew extends BluetoothCommunication{
private final UUID WEIGHT_MEASUREMENT_SERVICE = BluetoothGattUuid.fromShortCode(0xffb0);
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION = BluetoothGattUuid.fromShortCode(0xffb2);
private final UUID CMD_AFTER_MEASUREMENT = BluetoothGattUuid.fromShortCode(0xffb1);
private final int MSG_LENGTH = 20;
private final byte[] HEADER_BYTES = { (byte)0xAB, (byte)0x2A };
private ScaleMeasurement currentMeasurement;
public BluetoothOneByoneNew(Context context) {
super(context);
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] data){
if(data == null){
Timber.e("Received an empty message");
return;
}
Timber.i("Received %s", new BigInteger(1, data).toString(16));
if(data.length < MSG_LENGTH){
Timber.e("Received a message too short");
return;
}
if(!(data[0] == HEADER_BYTES[0] && data[1] == HEADER_BYTES[1])){
Timber.e("Unrecognized message received from scale.");
}
float weight;
int impedance;
switch(data[2]){
case (byte)0x00:
// real time measurement OR historic measurement
// real time has the exact same format of 0x80, but we can ignore it
// we want to capture the historic measures
// filter out real time measurments
if (data[7] != (byte)0x80){
Timber.i("Received real-time measurement. Skipping.");
break;
}
Date time = getTimestamp32(data, 3);
weight = Converters.fromUnsignedInt24Be(data, 8) & 0x03ffff;
weight /= 1000;
impedance = Converters.fromUnsignedInt16Be(data, 15);
ScaleMeasurement historicMeasurement = new ScaleMeasurement();
int assignableUserId = OpenScale.getInstance().getAssignableUser(weight);
if(assignableUserId == -1){
Timber.i("Discarding historic measurement: no user found with intelligent user recognition");
break;
}
populateMeasurement(assignableUserId, historicMeasurement, impedance, weight);
historicMeasurement.setDateTime(time);
addScaleMeasurement(historicMeasurement);
Timber.i("Added historic measurement. Weight: %s, impedance: %s, timestamp: %s", weight, impedance, time.toString());
break;
case (byte)0x80:
// final measurement
currentMeasurement = new ScaleMeasurement();
weight = Converters.fromUnsignedInt24Be(data, 3) & 0x03ffff;
weight = weight / 1000;
currentMeasurement.setWeight(weight);
Timber.d("Weight: %s", weight);
break;
case (byte)0x01:
impedance = Converters.fromUnsignedInt16Be(data, 4);
Timber.d("impedance: %s", impedance);
if(currentMeasurement == null){
Timber.e("Received impedance value without weight");
break;
}
float measurementWeight = currentMeasurement.getWeight();
ScaleUser user = OpenScale.getInstance().getSelectedScaleUser();
populateMeasurement(user.getId(), currentMeasurement, impedance, measurementWeight);
addScaleMeasurement(currentMeasurement);
resumeMachineState();
break;
default:
Timber.e("Unrecognized message receveid");
}
}
private void populateMeasurement(int userId, ScaleMeasurement measurement, int impedance, float weight) {
if(userId == -1){
Timber.e("Discarding measurement population since invalid user");
return;
}
ScaleUser user = OpenScale.getInstance().getScaleUser(userId);
float cmHeight = Converters.fromCentimeter(user.getBodyHeight(), user.getMeasureUnit());
OneByoneNewLib onebyoneLib = new OneByoneNewLib(getUserGender(user), user.getAge(), cmHeight, user.getActivityLevel().toInt());
measurement.setUserId(userId);
measurement.setWeight(weight);
measurement.setDateTime(Calendar.getInstance().getTime());
measurement.setFat(onebyoneLib.getBodyFatPercentage(weight, impedance));
measurement.setWater(onebyoneLib.getWaterPercentage(weight, impedance));
measurement.setBone(onebyoneLib.getBoneMass(weight, impedance));
measurement.setVisceralFat(onebyoneLib.getVisceralFat(weight));
measurement.setMuscle(onebyoneLib.getSkeletonMusclePercentage(weight, impedance));
measurement.setLbm(onebyoneLib.getLBM(weight, impedance));
}
@Override
public String driverName() {
return "OneByoneNew";
}
@Override
protected boolean onNextStep(int stepNr) {
switch(stepNr){
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION);
break;
case 1:
// Setup notification on new weight
sendWeightRequest();
// Update the user history on the scale
// Priority given to the current user
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
sendUsersHistory(currentUser.getId());
// We wait for the response
stopMachineState();
break;
case 2:
// After the measurement took place, we store the data and send back to the scale
sendUsersHistory(OpenScale.getInstance().getSelectedScaleUserId());
break;
default:
return false;
}
return true;
}
private void sendWeightRequest() {
byte[] msgSetup = new byte[MSG_LENGTH];
setupMeasurementMessage(msgSetup, 0);
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_AFTER_MEASUREMENT, msgSetup, true);
}
private void sendUsersHistory(int priorityUser){
List<ScaleUser> scaleUsers = OpenScale.getInstance().getScaleUserList();
Collections.sort(scaleUsers, (ScaleUser u1, ScaleUser u2) -> {
if(u1.getId() == priorityUser) return -9999;
if(u2.getId() == priorityUser) return 9999;
Date u1LastMeasureDate = OpenScale.getInstance().getLastScaleMeasurement(u1.getId()).getDateTime();
Date u2LastMeasureDate = OpenScale.getInstance().getLastScaleMeasurement(u2.getId()).getDateTime();
return u1LastMeasureDate.compareTo(u2LastMeasureDate);
}
);
byte[] msg = new byte[MSG_LENGTH];
int msgCounter = 0;
for(int i = 0; i < scaleUsers.size(); i++){
ScaleUser user = scaleUsers.get(i);
ScaleMeasurement lastMeasure = OpenScale.getInstance().getLastScaleMeasurement(user.getId());
float weight = 0;
int impedance = 0;
if(lastMeasure != null){
weight = lastMeasure.getWeight();
impedance = getImpedanceFromLBM(user, lastMeasure);
}
int entryPosition = i % 2;
if (entryPosition == 0){
msg = new byte[MSG_LENGTH];
msgCounter ++;
msg[0] = HEADER_BYTES[0];
msg[1] = HEADER_BYTES[1];
msg[2] = (byte) scaleUsers.size();
msg[3] = (byte) msgCounter;
}
setMeasurementEntry(msg, 4 + entryPosition * 7, i + 1,
Math.round(user.getBodyHeight()),
weight,
getUserGender(user),
user.getAge(),
impedance,
true);
if (entryPosition == 1 || i + 1 == scaleUsers.size()){
msg[18] = (byte)0xD4;
msg[19] = d4Checksum(msg, 0, MSG_LENGTH);
writeBytes(WEIGHT_MEASUREMENT_SERVICE, CMD_AFTER_MEASUREMENT, msg, true);
}
}
}
private void setMeasurementEntry(byte[] msg, int offset, int entryNum, int height, float weight, int sex, int age, int impedance, boolean impedanceLe){
// The scale wants a value rounded to the first decimal place
// Otherwise we receive always a UP/DOWN arrow since we would communicate
// AB.CX instead of AB.D0 where D0 is the approximation of CX and it is what the scale uses
// to compute the UP/DOWN arrows
int roundedWeight = Math.round( weight * 10) * 10;
msg[offset] = (byte)(entryNum & 0xFF);
msg[offset+1] = (byte)(height & 0xFF);
Converters.toInt16Be(msg, offset+2, roundedWeight);
msg[offset+4] = (byte)(((sex & 0xFF) << 7) + (age & 0x7F));
if(impedanceLe) {
msg[offset + 5] = (byte) (impedance >> 8);
msg[offset + 6] = (byte) impedance;
} else {
msg[offset + 5] = (byte) impedance;
msg[offset + 6] = (byte) (impedance >> 8);
}
}
private void setTimestamp32(byte[] msg, int offset){
long timestamp = System.currentTimeMillis()/1000L;
Converters.toInt32Be(msg, offset, timestamp);
}
private Date getTimestamp32(byte[] msg, int offset){
long timestamp = Converters.fromUnsignedInt32Be(msg, offset);
return new Date(timestamp * 1000);
}
private boolean setupMeasurementMessage(byte[] msg, int offset){
if(offset + MSG_LENGTH > msg.length){
return false;
}
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
Converters.WeightUnit weightUnit = currentUser.getScaleUnit();
msg[offset] = HEADER_BYTES[0];
msg[offset+1] = HEADER_BYTES[1];
setTimestamp32(msg, offset+2);
// This byte has been left empty in all the observations, unknown meaning
msg[offset+6] = 0;
msg[offset+7] = (byte) weightUnit.toInt();
int userId = currentUser.getId();
// We send the last measurement or if not present an empty one
ScaleMeasurement lastMeasure = OpenScale.getInstance().getLastScaleMeasurement(userId);
float weight = 0;
int impedance = 0;
if(lastMeasure != null){
weight = lastMeasure.getWeight();
impedance = getImpedanceFromLBM(currentUser, lastMeasure);
}
setMeasurementEntry(msg, offset+8,
userId,
Math.round(currentUser.getBodyHeight()),
weight,
getUserGender(currentUser),
currentUser.getAge(),
impedance,
false
);
// Blank bytes after the empty measurement
msg[offset + 18] = (byte) 0xD7;
msg[offset+19] = d7Checksum(msg, offset+2, 17);
return true;
}
private int getUserGender(ScaleUser user){
// Custom function since the toInt() gives the opposite values
return user.getGender().isMale() ? 1 : 0;
}
private byte d4Checksum(byte[] msg, int offset, int length){
byte sum = sumChecksum(msg, offset + 2, length - 2);
// Remove impedance MSB first entry
sum -= msg[offset+9];
// Remove second entry weight
sum -= msg[offset+13];
sum -= msg[offset+14];
// Remove impedance MSB second entry
sum -= msg[offset+16];
return sum;
}
private byte d7Checksum(byte[] msg, int offset, int length){
byte sum = sumChecksum(msg, offset+2, length-2);
// Remove impedance MSB
sum -= msg[offset+14];
return sum;
}
// Since we need to send the impedance to the scale the next time,
// we obtain it from the previous measurement using the LBM
public int getImpedanceFromLBM(ScaleUser user, ScaleMeasurement measurement) {
float finalLbm = measurement.getLbm();
float postImpedanceLbm = finalLbm + user.getAge() * 0.0542F;
float preImpedanceLbm = user.getBodyHeight() / 100 * user.getBodyHeight() / 100 * 9.058F + 12.226F + measurement.getWeight() * 0.32F;
return Math.round((preImpedanceLbm - postImpedanceLbm) / 0.0068F);
}
}

View File

@@ -0,0 +1,138 @@
/* Copyright (C) 2021 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
/*
* Based on source-code by weliem/blessed-android
*/
package com.health.openscale.core.bluetooth;
import android.content.Context;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.welie.blessed.BluetoothBytesParser;
import java.util.UUID;
import timber.log.Timber;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
public class BluetoothSanitasSBF72 extends BluetoothStandardWeightProfile {
private String deviceName;
private static final UUID SERVICE_SBF72_CUSTOM = BluetoothGattUuid.fromShortCode(0xffff);
private static final UUID CHARACTERISTIC_SCALE_SETTINGS = BluetoothGattUuid.fromShortCode(0x0000);
private static final UUID CHARACTERISTIC_USER_LIST = BluetoothGattUuid.fromShortCode(0x0001);
private static final UUID CHARACTERISTIC_ACTIVITY_LEVEL = BluetoothGattUuid.fromShortCode(0x0004);
private static final UUID CHARACTERISTIC_REFER_WEIGHT_BF = BluetoothGattUuid.fromShortCode(0x000b);
private static final UUID CHARACTERISTIC_TAKE_MEASUREMENT = BluetoothGattUuid.fromShortCode(0x0006);
public BluetoothSanitasSBF72(Context context, String name) {
super(context);
deviceName = name;
}
@Override
public String driverName() {
return deviceName;
}
@Override
protected int getVendorSpecificMaxUserCount() {
return 8;
}
@Override
protected void enterScaleUserConsentUi(int appScaleUserId, int scaleUserIndex) {
//Requests the scale to display the pin for the user in it's display.
//As parameter we need to send a pin-index to the custom user-list characteristic.
//For user with index 1 the pin-index is 0x11, for user with index 2 it is 0x12 and so on.
int scalePinIndex = scaleUserIndex + 16;
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(scalePinIndex, FORMAT_UINT8);
writeBytes(SERVICE_SBF72_CUSTOM, CHARACTERISTIC_USER_LIST, parser.getValue());
//opens the input screen for the pin in the app
super.enterScaleUserConsentUi(appScaleUserId, scaleUserIndex);
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
if (characteristic.equals(CHARACTERISTIC_USER_LIST)) {
//the if condition is to catch the response to "display-pin-on-scale", because this response would produce an error in handleVendorSpecificUserList().
if (value != null && value.length > 0 && value[0] != 17) {
handleVendorSpecificUserList(value);
}
}
else {
super.onBluetoothNotify(characteristic, value);
}
}
@Override
protected ScaleMeasurement bodyCompositionMeasurementToScaleMeasurement(byte[] value) {
ScaleMeasurement measurement = super.bodyCompositionMeasurementToScaleMeasurement(value);
float weight = measurement.getWeight();
if (weight == 0.f && previousMeasurement != null) {
weight = previousMeasurement.getWeight();
}
if (weight != 0.f) {
float water = Math.round(((measurement.getWater() / weight) * 10000.f))/100.f;
measurement.setWater(water);
}
return measurement;
}
@Override
protected void setNotifyVendorSpecificUserList() {
if (setNotificationOn(SERVICE_SBF72_CUSTOM, CHARACTERISTIC_USER_LIST)) {
Timber.d("setNotifyVendorSpecificUserList() OK");
} else {
Timber.d("setNotifyVendorSpecificUserList() FAILED");
}
}
@Override
protected synchronized void requestVendorSpecificUserList() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(0, FORMAT_UINT8);
writeBytes(SERVICE_SBF72_CUSTOM, CHARACTERISTIC_USER_LIST, parser.getValue());
}
@Override
protected void writeActivityLevel() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int activityLevel = this.selectedUser.getActivityLevel().toInt() + 1;
Timber.d(String.format("activityLevel: %d", activityLevel));
parser.setIntValue(activityLevel, FORMAT_UINT8);
writeBytes(SERVICE_SBF72_CUSTOM, CHARACTERISTIC_ACTIVITY_LEVEL, parser.getValue());
}
@Override
protected void writeInitials() {
Timber.d("Write user initials is not supported by " + deviceName + "!");
}
@Override
protected synchronized void requestMeasurement() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setIntValue(0, FORMAT_UINT8);
writeBytes(SERVICE_SBF72_CUSTOM, CHARACTERISTIC_TAKE_MEASUREMENT, parser.getValue());
}
}

View File

@@ -115,7 +115,7 @@ public class BluetoothSenssun extends BluetoothCommunication {
switch(data[5]) {
case (byte)0xAA:
case (byte)0xA0:
if (values > 1) {
if (weightStabilized) {
return;
}
if (!stepMessageDisplayed) {
@@ -127,13 +127,9 @@ public class BluetoothSenssun extends BluetoothCommunication {
Timber.d("the byte is %d stable is %s", (data[5] & 0xff), weightStabilized ? "true": "false");
lastWeight = ((data[1] & 0xff) << 8) | (data[2] & 0xff);
if (lastWeight > 0) {
sendMessage(R.string.info_measuring, lastWeight / 10.0f);
}
if (weightStabilized) {
values |= 1;
sendMessage(R.string.info_measuring, lastWeight / 10.0f);
synchroniseUser();
}
break;

View File

@@ -0,0 +1,119 @@
package com.health.openscale.core.bluetooth;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.welie.blessed.BluetoothCentralManager;
import com.welie.blessed.BluetoothCentralManagerCallback;
import com.welie.blessed.BluetoothPeripheral;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
import timber.log.Timber;
public class BluetoothSinocare extends BluetoothCommunication {
private static final int MANUFACTURER_DATA_ID = 0xff64; // 16-bit little endian "header"
private static final int WEIGHT_MSB = 10;
private static final int WEIGHT_LSB = 9;
private static final int CHECKSUM_INDEX = 16;
// the number of consecutive times the same weight should be seen before it is considered "final"
private static final int WEIGHT_TRIGGER_THRESHOLD = 9;
//these values are used to check for whether the scale's weight reading has leveled out since
// the scale doesnt appear to communicate when it has a solid reading.
private static int last_seen_weight = 0;
private static int last_wait_repeat_count = 0;
private BluetoothCentralManager central;
private final BluetoothCentralManagerCallback btCallback = new BluetoothCentralManagerCallback() {
@Override
public void onDiscoveredPeripheral(@NotNull BluetoothPeripheral peripheral, @NotNull ScanResult scanResult) {
SparseArray<byte[]> manufacturerSpecificData = scanResult.getScanRecord().getManufacturerSpecificData();
byte[] data = manufacturerSpecificData.get(MANUFACTURER_DATA_ID);
float divider = 100.0f;
byte checksum = 0x00;
//the checksum here only covers the data that is between the MAC address and the checksum
//this should be bytes at indices 6-15 (both inclusive)
for (int i = 6; i < CHECKSUM_INDEX; i++)
checksum ^= data[i];
if (data[CHECKSUM_INDEX] != checksum) {
Timber.d("Checksum error, got %x, expected %x", data[CHECKSUM_INDEX] & 0xff, checksum & 0xff);
return;
}
// mac address is first 6 bytes, might be helpful if this needs to be capable of handling
// multiple scales at once. Is this a priority?
// byte[] macAddress = ;
//this is the "raw" weight as an integer number of dekagrams (1 dekagram is 0.01kg or 10 grams),
// regardless of what unit the scale is set to
int weight = data[WEIGHT_MSB] & 0xff;
weight = weight << 8 | (data[WEIGHT_LSB] & 0xff);
if (weight > 0){
if (weight != last_seen_weight) {
//record the current weight and reset the count for mow many times that value has been seen
last_seen_weight = weight;
last_wait_repeat_count = 1;
} else if (weight == last_seen_weight && last_wait_repeat_count >= WEIGHT_TRIGGER_THRESHOLD){
// record the weight
ScaleMeasurement entry = new ScaleMeasurement();
entry.setWeight(last_seen_weight / divider);
addScaleMeasurement(entry);
disconnect();
} else {
//increment the counter for the number of times this weight value has been seen
last_wait_repeat_count += 1;
}
}
}
};
public BluetoothSinocare(Context context)
{
super(context);
central = new BluetoothCentralManager(context, btCallback, new Handler(Looper.getMainLooper()));
}
@Override
public String driverName() {
return "Sinocare";
}
@Override
public void connect(String macAddress) {
Timber.d("Mac address: %s", macAddress);
List<ScanFilter> filters = new LinkedList<ScanFilter>();
ScanFilter.Builder b = new ScanFilter.Builder();
b.setDeviceAddress(macAddress);
b.setDeviceName("Weight Scale");
b.setManufacturerData(MANUFACTURER_DATA_ID, null, null);
filters.add(b.build());
central.scanForPeripheralsUsingFilters(filters);
}
@Override
public void disconnect() {
if (central != null)
central.stopScan();
central = null;
super.disconnect();
}
@Override
protected boolean onNextStep(int stepNr) {
return false;
}
}

View File

@@ -17,44 +17,68 @@
/*
* Based on source-code by weliem/blessed-android
*/
package com.health.openscale.core.bluetooth;
import android.content.Context;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.welie.blessed.BluetoothBytesParser;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
import timber.log.Timber;
package com.health.openscale.core.bluetooth;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT32;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT16;
import static com.welie.blessed.BluetoothBytesParser.FORMAT_UINT8;
public class BluetoothStandardWeightProfile extends BluetoothCommunication {
private int CURRENT_USER_CONSENT = 3289;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Pair;
import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import com.health.openscale.core.datatypes.ScaleUser;
import com.health.openscale.core.utils.Converters;
import com.welie.blessed.BluetoothBytesParser;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Random;
import java.util.UUID;
import java.util.Vector;
import timber.log.Timber;
public abstract class BluetoothStandardWeightProfile extends BluetoothCommunication {
// UDS control point codes
private static final byte UDS_CP_REGISTER_NEW_USER = 0x01;
private static final byte UDS_CP_CONSENT = 0x02;
private static final byte UDS_CP_DELETE_USER_DATA = 0x03;
private static final byte UDS_CP_LIST_ALL_USERS = 0x04;
private static final byte UDS_CP_DELETE_USERS = 0x05;
private static final byte UDS_CP_RESPONSE = 0x20;
protected static final byte UDS_CP_REGISTER_NEW_USER = 0x01;
protected static final byte UDS_CP_CONSENT = 0x02;
protected static final byte UDS_CP_DELETE_USER_DATA = 0x03;
protected static final byte UDS_CP_LIST_ALL_USERS = 0x04;
protected static final byte UDS_CP_DELETE_USERS = 0x05;
protected static final byte UDS_CP_RESPONSE = 0x20;
// UDS response codes
private static final byte UDS_CP_RESP_VALUE_SUCCESS = 0x01;
private static final byte UDS_CP_RESP_OP_CODE_NOT_SUPPORTED = 0x02;
private static final byte UDS_CP_RESP_INVALID_PARAMETER = 0x03;
private static final byte UDS_CP_RESP_OPERATION_FAILED = 0x04;
private static final byte UDS_CP_RESP_USER_NOT_AUTHORIZED = 0x05;
protected static final byte UDS_CP_RESP_VALUE_SUCCESS = 0x01;
protected static final byte UDS_CP_RESP_OP_CODE_NOT_SUPPORTED = 0x02;
protected static final byte UDS_CP_RESP_INVALID_PARAMETER = 0x03;
protected static final byte UDS_CP_RESP_OPERATION_FAILED = 0x04;
protected static final byte UDS_CP_RESP_USER_NOT_AUTHORIZED = 0x05;
SharedPreferences prefs;
protected boolean registerNewUser;
ScaleUser selectedUser;
ScaleMeasurement previousMeasurement;
protected boolean haveBatteryService;
protected Vector<ScaleUser> scaleUserList;
public BluetoothStandardWeightProfile(Context context) {
super(context);
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.selectedUser = OpenScale.getInstance().getSelectedScaleUser();
this.registerNewUser = false;
previousMeasurement = null;
haveBatteryService = false;
scaleUserList = new Vector<ScaleUser>();
}
@Override
@@ -62,42 +86,126 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
return "Bluetooth Standard Weight Profile";
}
protected abstract int getVendorSpecificMaxUserCount();
private enum SM_STEPS {
START,
READ_DEVICE_MANUFACTURER,
READ_DEVICE_MODEL,
WRITE_CURRENT_TIME,
SET_NOTIFY_WEIGHT_MEASUREMENT,
SET_NOTIFY_BODY_COMPOSITION_MEASUREMENT,
SET_NOTIFY_CHANGE_INCREMENT,
SET_INDICATION_USER_CONTROL_POINT,
SET_NOTIFY_BATTERY_LEVEL,
READ_BATTERY_LEVEL,
SET_NOTIFY_VENDOR_SPECIFIC_USER_LIST,
REQUEST_VENDOR_SPECIFIC_USER_LIST,
REGISTER_NEW_SCALE_USER,
SELECT_SCALE_USER,
SET_SCALE_USER_DATA,
REQUEST_MEASUREMENT,
MAX_STEP
}
@Override
protected boolean onNextStep(int stepNr) {
switch (stepNr) {
case 0:
// Read manufacturer and model number from the Device Information Service
if (stepNr > SM_STEPS.MAX_STEP.ordinal()) {
Timber.d( "WARNING: stepNr == " + stepNr + " outside range, must be from 0 to " + SM_STEPS.MAX_STEP.ordinal());
stepNr = SM_STEPS.MAX_STEP.ordinal();
}
SM_STEPS step = SM_STEPS.values()[stepNr];
Timber.d("stepNr: " + stepNr + " " + step);
switch (step) {
case START:
break;
case READ_DEVICE_MANUFACTURER:
// Read manufacturer from the Device Information Service
readBytes(BluetoothGattUuid.SERVICE_DEVICE_INFORMATION, BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING);
break;
case READ_DEVICE_MODEL:
// Read model number from the Device Information Service
readBytes(BluetoothGattUuid.SERVICE_DEVICE_INFORMATION, BluetoothGattUuid.CHARACTERISTIC_MODEL_NUMBER_STRING);
break;
case 1:
// Write the current time
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setCurrentTime(Calendar.getInstance());
writeBytes(BluetoothGattUuid.SERVICE_CURRENT_TIME, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME, parser.getValue());
case WRITE_CURRENT_TIME:
writeCurrentTime();
break;
case 2:
case SET_NOTIFY_WEIGHT_MEASUREMENT:
// Turn on notification for Weight Service
setNotificationOn(BluetoothGattUuid.SERVICE_WEIGHT_SCALE, BluetoothGattUuid.CHARACTERISTIC_WEIGHT_MEASUREMENT);
break;
case 3:
case SET_NOTIFY_BODY_COMPOSITION_MEASUREMENT:
// Turn on notification for Body Composition Service
setNotificationOn(BluetoothGattUuid.SERVICE_BODY_COMPOSITION, BluetoothGattUuid.CHARACTERISTIC_BODY_COMPOSITION_MEASUREMENT);
break;
case 4:
// Turn on notification for User Data Service
case SET_NOTIFY_CHANGE_INCREMENT:
// Turn on notification for User Data Service Change Increment
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT);
setNotificationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT);
break;
case 5:
case SET_INDICATION_USER_CONTROL_POINT:
// Turn on notification for User Control Point
setIndicationOn(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT);
break;
case SET_NOTIFY_BATTERY_LEVEL:
// Turn on notifications for Battery Service
setNotificationOn(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL);
if (setNotificationOn(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL)) {
haveBatteryService = true;
}
else {
haveBatteryService = false;
}
break;
case 6:
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
registerUser(CURRENT_USER_CONSENT);
setUser(selectedUser.getId(), CURRENT_USER_CONSENT);
case READ_BATTERY_LEVEL:
// read Battery Service
if (haveBatteryService) {
readBytes(BluetoothGattUuid.SERVICE_BATTERY_LEVEL, BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL);
}
break;
case SET_NOTIFY_VENDOR_SPECIFIC_USER_LIST:
setNotifyVendorSpecificUserList();
break;
case REQUEST_VENDOR_SPECIFIC_USER_LIST:
scaleUserList.clear();
requestVendorSpecificUserList();
stopMachineState();
break;
case REGISTER_NEW_SCALE_USER:
int userId = this.selectedUser.getId();
int consentCode = getUserScaleConsent(userId);
int userIndex = getUserScaleIndex(userId);
if (consentCode == -1 || userIndex == -1) {
registerNewUser = true;
}
if (registerNewUser) {
Random randomFactory = new Random();
consentCode = randomFactory.nextInt(10000);
storeUserScaleConsentCode(userId, consentCode);
registerUser(consentCode);
stopMachineState();
}
break;
case SELECT_SCALE_USER:
Timber.d("Select user on scale!");
setUser(this.selectedUser.getId());
stopMachineState();
break;
case SET_SCALE_USER_DATA:
if (registerNewUser) {
writeUserDataToScale();
// stopping machine state to have all user data written, before the reference measurment starts, otherwise the scale might not store the user
stopMachineState();
// reading CHARACTERISTIC_CHANGE_INCREMENT to resume machine state
readBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT);
}
break;
case REQUEST_MEASUREMENT:
if (registerNewUser) {
requestMeasurement();
stopMachineState();
sendMessage(R.string.info_step_on_scale_for_reference, 0);
}
break;
default:
return false;
@@ -106,6 +214,15 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
return true;
}
protected void writeUserDataToScale() {
writeBirthday();
writeGender();
writeHeight();
writeActivityLevel();
writeInitials();
setChangeIncrement();
}
@Override
public void onBluetoothNotify(UUID characteristic, byte[] value) {
BluetoothBytesParser parser = new BluetoothBytesParser(value);
@@ -123,6 +240,9 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_BATTERY_LEVEL)) {
int batteryLevel = parser.getIntValue(FORMAT_UINT8);
Timber.d(String.format("Received battery level %d%%", batteryLevel));
if (batteryLevel <= 10) {
sendMessage(R.string.info_scale_low_battery, batteryLevel);
}
}
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_MANUFACTURER_NAME_STRING)) {
String manufacturer = parser.getStringValue(0);
@@ -132,41 +252,83 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
String modelNumber = parser.getStringValue(0);
Timber.d(String.format("Received modelnumber: %s", modelNumber));
}
else if(characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT)) {
if(value[0]==UDS_CP_RESPONSE) {
switch (value[1]) {
case UDS_CP_REGISTER_NEW_USER:
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
int userIndex = value[3];
Timber.d(String.format("Created user %d", userIndex));
} else {
Timber.e("ERROR: could not register new user");
}
break;
case UDS_CP_CONSENT:
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
Timber.d("Success user consent");
} else if (value[2] == UDS_CP_RESP_USER_NOT_AUTHORIZED) {
Timber.e("Not authorized");
}
break;
default:
Timber.e("Unhandled response");
break;
}
}
} else {
Timber.d(String.format("Got data: <%s>", byteInHex(value)));
else if (characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT)) {
handleUserControlPointNotify(value);
}
else if (characteristic.equals(BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT)) {
int increment = parser.getIntValue(FORMAT_UINT32);
Timber.d(String.format("Notification from CHARACTERISTIC_CHANGE_INCREMENT, value: %s", increment));
resumeMachineState();
}
else {
Timber.d(String.format("Notification from unhandled characteristic: %s, value: [%s]",
characteristic.toString(), byteInHex(value)));
}
}
private void handleWeightMeasurement(byte[] value) {
protected void handleUserControlPointNotify(byte[] value) {
if(value[0]==UDS_CP_RESPONSE) {
switch (value[1]) {
case UDS_CP_LIST_ALL_USERS:
Timber.d("UDS_CP_LIST_ALL_USERS value [" + byteInHex(value) + "]");
break;
case UDS_CP_REGISTER_NEW_USER:
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
int userIndex = value[3];
int userId = this.selectedUser.getId();
Timber.d(String.format("UDS_CP_REGISTER_NEW_USER: Created scale user index: "
+ "%d (app user id: %d)", userIndex, userId));
storeUserScaleIndex(userId, userIndex);
resumeMachineState();
} else {
Timber.e("UDS_CP_REGISTER_NEW_USER: ERROR: could not register new scale user, code: " + value[2]);
}
break;
case UDS_CP_CONSENT:
if (registerNewUser) {
Timber.d("UDS_CP_CONSENT: registerNewUser==true, value[2] == " + value[2]);
resumeMachineState();
break;
}
if (value[2] == UDS_CP_RESP_VALUE_SUCCESS) {
Timber.d("UDS_CP_CONSENT: Success user consent");
resumeMachineState();
} else if (value[2] == UDS_CP_RESP_USER_NOT_AUTHORIZED) {
Timber.e("UDS_CP_CONSENT: Not authorized");
enterScaleUserConsentUi(this.selectedUser.getId(), getUserScaleIndex(this.selectedUser.getId()));
}
else {
Timber.e("UDS_CP_CONSENT: unhandled, code: " + value[2]);
}
break;
default:
Timber.e("CHARACTERISTIC_USER_CONTROL_POINT: Unhandled response code "
+ value[1] + " value [" + byteInHex(value) + "]");
break;
}
}
else {
Timber.d("CHARACTERISTIC_USER_CONTROL_POINT: non-response code " + value[0]
+ " value [" + byteInHex(value) + "]");
}
}
protected ScaleMeasurement weightMeasurementToScaleMeasurement(byte[] value) {
String prefix = "weightMeasurementToScaleMeasurement() ";
Timber.d(String.format(prefix + "value: [%s]", byteInHex(value)));
BluetoothBytesParser parser = new BluetoothBytesParser(value);
final int flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
boolean isKg = (flags & 0x01) == 0;
final boolean timestampPresent = (flags & 0x02) > 0;
final boolean userIDPresent = (flags & 0x04) > 0;
final boolean bmiAndHeightPresent = (flags & 0x08) > 0;
Timber.d(String.format(prefix + "flags: 0x%02x ", flags)
+ "[" + (isKg ? "SI" : "Imperial")
+ (timestampPresent ? ", timestamp" : "")
+ (userIDPresent ? ", userID" : "")
+ (bmiAndHeightPresent ? ", bmiAndHeight" : "")
+ "], " + String.format("reserved flags: 0x%02x ", flags & 0xf0));
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
@@ -175,29 +337,55 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
// Get weight
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * weightMultiplier;
Timber.d(prefix+ "weight: " + weightValue);
scaleMeasurement.setWeight(weightValue);
if(timestampPresent) {
Date timestamp = parser.getDateTime();
Timber.d(prefix + "timestamp: " + timestamp.toString());
scaleMeasurement.setDateTime(timestamp);
}
if(userIDPresent) {
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
Timber.d(String.format("User id: %i", userID));
int scaleUserIndex = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
int userID = getUserIdFromScaleIndex(scaleUserIndex);
Timber.d(String.format(prefix + "scale user index: %d (app user id: %d)", scaleUserIndex, userID));
if (userID != -1) {
scaleMeasurement.setUserId(userID);
}
if (registerNewUser) {
Timber.d(String.format(prefix + "Setting initial weight for user %s to: %s and registerNewUser to false", userID,
weightValue));
if (selectedUser.getId() == userID) {
this.selectedUser.setInitialWeight(weightValue);
OpenScale.getInstance().updateScaleUser(selectedUser);
}
registerNewUser = false;
resumeMachineState();
}
}
if(bmiAndHeightPresent) {
float BMI = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
Timber.d(prefix + "BMI: " + BMI);
float heightInMeters = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.001f;
Timber.d(prefix + "heightInMeters: " + heightInMeters);
}
Timber.d(String.format("Got weight: %s", weightValue));
addScaleMeasurement(scaleMeasurement);
return scaleMeasurement;
}
private void handleBodyCompositionMeasurement(byte[] value) {
protected void handleWeightMeasurement(byte[] value) {
mergeWithPreviousScaleMeasurement(weightMeasurementToScaleMeasurement(value));
}
protected ScaleMeasurement bodyCompositionMeasurementToScaleMeasurement(byte[] value) {
String prefix = "bodyCompositionMeasurementToScaleMeasurement() ";
Timber.d(String.format(prefix + "value: [%s]", byteInHex(value)));
BluetoothBytesParser parser = new BluetoothBytesParser(value);
final int flags = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
boolean isKg = (flags & 0x0001) == 0;
float massMultiplier = (float) (isKg ? 0.005 : 0.01);
@@ -213,78 +401,187 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
boolean weightPresent = (flags & 0x0400) > 0;
boolean heightPresent = (flags & 0x0800) > 0;
boolean multiPacketMeasurement = (flags & 0x1000) > 0;
Timber.d(String.format(prefix + "flags: 0x%02x ", flags)
+ "[" + (isKg ? "SI" : "Imperial")
+ (timestampPresent ? ", timestamp" : "")
+ (userIDPresent ? ", userID" : "")
+ (bmrPresent ? ", bmr" : "")
+ (musclePercentagePresent ? ", musclePercentage" : "")
+ (muscleMassPresent ? ", muscleMass" : "")
+ (fatFreeMassPresent ? ", fatFreeMass" : "")
+ (softLeanMassPresent ? ", softLeanMass" : "")
+ (bodyWaterMassPresent ? ", bodyWaterMass" : "")
+ (impedancePresent ? ", impedance" : "")
+ (weightPresent ? ", weight" : "")
+ (heightPresent ? ", height" : "")
+ (multiPacketMeasurement ? ", multiPacketMeasurement" : "")
+ "], " + String.format("reserved flags: 0x%04x ", flags & 0xe000));
ScaleMeasurement scaleMeasurement = new ScaleMeasurement();
float bodyFatPercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
Timber.d(prefix + "bodyFatPercentage: " + bodyFatPercentage);
scaleMeasurement.setFat(bodyFatPercentage);
// Read timestamp if present
if (timestampPresent) {
Date timestamp = parser.getDateTime();
Timber.d(prefix + "timestamp: " + timestamp.toString());
scaleMeasurement.setDateTime(timestamp);
}
// Read userID if present
if (userIDPresent) {
int userID = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
Timber.d(String.format("user id: %i", userID));
int scaleUserIndex = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT8);
int userID = getUserIdFromScaleIndex(scaleUserIndex);
Timber.d(String.format(prefix + "scale user index: %d (app user id: %d)", scaleUserIndex, userID));
if (userID != -1) {
scaleMeasurement.setUserId(userID);
}
}
// Read bmr if present
if (bmrPresent) {
int bmrInJoules = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
int bmrInKcal = Math.round(((bmrInJoules / 4.1868f) * 10.0f) / 10.0f);
Timber.d(prefix + "bmrInJoules: " + bmrInJoules + " bmrInKcal: " + bmrInKcal);
}
// Read musclePercentage if present
if (musclePercentagePresent) {
float musclePercentage = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
Timber.d(prefix + "musclePercentage: " + musclePercentage);
scaleMeasurement.setMuscle(musclePercentage);
}
// Read muscleMass if present
if (muscleMassPresent) {
float muscleMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
Timber.d(prefix + "muscleMass: " + muscleMass);
}
// Read fatFreeMassPresent if present
if (fatFreeMassPresent) {
float fatFreeMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
Timber.d(prefix + "fatFreeMass: " + fatFreeMass);
}
// Read softleanMass if present
float softLeanMass = 0.0f;
if (softLeanMassPresent) {
float softLeanMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
softLeanMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
Timber.d(prefix + "softLeanMass: " + softLeanMass);
}
// Read bodyWaterMass if present
if (bodyWaterMassPresent) {
float bodyWaterMass = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
Timber.d(prefix + "bodyWaterMass: " + bodyWaterMass);
scaleMeasurement.setWater(bodyWaterMass);
}
// Read impedance if present
if (impedancePresent) {
float impedance = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * 0.1f;
Timber.d(prefix + "impedance: " + impedance);
}
// Read weight if present
float weightValue = 0.0f;
if (weightPresent) {
float weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
weightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16) * massMultiplier;
Timber.d(prefix + "weightValue: " + weightValue);
scaleMeasurement.setWeight(weightValue);
}
else {
if (previousMeasurement != null) {
weightValue = previousMeasurement.getWeight();
if (weightValue > 0) {
weightPresent = true;
}
}
}
// calculate lean body mass and bone mass
if (weightPresent && softLeanMassPresent) {
float fatMass = weightValue * bodyFatPercentage / 100.0f;
float leanBodyMass = weightValue - fatMass;
float boneMass = leanBodyMass - softLeanMass;
scaleMeasurement.setLbm(leanBodyMass);
scaleMeasurement.setBone(boneMass);
}
// Read height if present
if (heightPresent) {
float heightValue = parser.getIntValue(BluetoothBytesParser.FORMAT_UINT16);
Timber.d(prefix + "heightValue: " + heightValue);
}
if (multiPacketMeasurement) {
Timber.e(prefix + "multiPacketMeasurement not supported!");
}
Timber.d(String.format("Got body composition: %s", byteInHex(value)));
addScaleMeasurement(scaleMeasurement);
return scaleMeasurement;
}
private void registerUser(int consentCode) {
protected void handleBodyCompositionMeasurement(byte[] value) {
mergeWithPreviousScaleMeasurement(bodyCompositionMeasurementToScaleMeasurement(value));
}
/**
* Bluetooth scales usually implement both "Weight Scale Feature" and "Body Composition Feature".
* It seems that scale first transmits weight measurement (with user index and timestamp) and
* later transmits body composition measurement (without user index and timestamp).
* If previous measurement contains user index and new measurements does not then merge them and
* store as one.
* disconnect() function must store previousMeasurement to openScale db (if present).
*
* @param newMeasurement the scale data that should be merged with previous measurement or
* stored as previous measurement.
*/
protected void mergeWithPreviousScaleMeasurement(ScaleMeasurement newMeasurement) {
if (previousMeasurement == null) {
if (newMeasurement.getUserId() == -1) {
addScaleMeasurement(newMeasurement);
}
else {
previousMeasurement = newMeasurement;
}
}
else {
if ((newMeasurement.getUserId() == -1) && (previousMeasurement.getUserId() != -1)) {
previousMeasurement.merge(newMeasurement);
addScaleMeasurement(previousMeasurement);
previousMeasurement = null;
}
else {
addScaleMeasurement(previousMeasurement);
if (newMeasurement.getUserId() == -1) {
addScaleMeasurement(newMeasurement);
previousMeasurement = null;
}
else {
previousMeasurement = newMeasurement;
}
}
}
}
@Override
public void disconnect() {
if (previousMeasurement != null) {
addScaleMeasurement(previousMeasurement);
previousMeasurement = null;
}
super.disconnect();
}
protected abstract void setNotifyVendorSpecificUserList();
protected abstract void requestVendorSpecificUserList();
protected void registerUser(int consentCode) {
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0,0,0});
parser.setIntValue(UDS_CP_REGISTER_NEW_USER, FORMAT_UINT8,0);
parser.setIntValue(consentCode, FORMAT_UINT16,1);
@@ -292,7 +589,7 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
}
private void setUser(int userIndex, int consentCode) {
protected void setUser(int userIndex, int consentCode) {
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[]{0,0,0,0});
parser.setIntValue(UDS_CP_CONSENT,FORMAT_UINT8,0);
parser.setIntValue(userIndex, FORMAT_UINT8,1);
@@ -300,4 +597,273 @@ public class BluetoothStandardWeightProfile extends BluetoothCommunication {
Timber.d(String.format("setUser userIndex: %d, consentCode: %d", userIndex, consentCode));
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT, parser.getValue());
}
protected synchronized void setUser(int userId) {
int userIndex = getUserScaleIndex(userId);
int consentCode = getUserScaleConsent(userId);
Timber.d(String.format("setting: userId %d, userIndex: %d, consent Code: %d ", userId, userIndex, consentCode));
setUser(userIndex, consentCode);
}
protected void deleteUser(int userIndex, int consentCode) {
setUser(userIndex, consentCode);
deleteUser();
}
protected void deleteUser() {
BluetoothBytesParser parser = new BluetoothBytesParser(new byte[] { 0 });
parser.setIntValue(UDS_CP_DELETE_USER_DATA, FORMAT_UINT8, 0);
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_CONTROL_POINT,
parser.getValue());
}
protected void writeCurrentTime() {
BluetoothBytesParser parser = new BluetoothBytesParser();
parser.setCurrentTime(Calendar.getInstance());
writeBytes(BluetoothGattUuid.SERVICE_CURRENT_TIME, BluetoothGattUuid.CHARACTERISTIC_CURRENT_TIME,
parser.getValue());
}
protected void writeBirthday() {
BluetoothBytesParser parser = new BluetoothBytesParser();
Calendar userBirthday = dateToCalender(this.selectedUser.getBirthday());
Timber.d(String.format("user Birthday: %tD", userBirthday));
parser.setDateTime(userBirthday);
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_DATE_OF_BIRTH,
Arrays.copyOfRange(parser.getValue(), 0, 4));
}
protected Calendar dateToCalender(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
protected void writeGender() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int gender = this.selectedUser.getGender().toInt();
Timber.d(String.format("gender: %d", gender));
parser.setIntValue(gender, FORMAT_UINT8);
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_GENDER,
parser.getValue());
}
protected void writeHeight() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int height = (int) this.selectedUser.getBodyHeight();
Timber.d(String.format("height: %d", height));
parser.setIntValue(height, FORMAT_UINT16);
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_USER_HEIGHT,
parser.getValue());
}
protected void writeActivityLevel() {
Timber.d("Write user activity level not implemented!");
}
protected void writeInitials() {
Timber.d("Write user initials not implemented!");
}
protected void setChangeIncrement() {
BluetoothBytesParser parser = new BluetoothBytesParser();
int i = 1;
Timber.d(String.format("Setting Change increment to %s", i));
parser.setIntValue(i, FORMAT_UINT32);
writeBytes(BluetoothGattUuid.SERVICE_USER_DATA, BluetoothGattUuid.CHARACTERISTIC_CHANGE_INCREMENT,
parser.getValue());
}
protected void requestMeasurement() {
Timber.d("Take measurement command not implemented!");
}
protected synchronized void storeUserScaleConsentCode(int userId, int consentCode) {
prefs.edit().putInt("userConsentCode" + userId, consentCode).apply();
}
protected synchronized int getUserScaleConsent(int userId) {
return prefs.getInt("userConsentCode" + userId, -1);
}
protected synchronized void storeUserScaleIndex(int userId, int userIndex) {
int currentUserIndex = getUserScaleIndex(userId);
if (currentUserIndex != -1) {
prefs.edit().putInt("userIdFromUserScaleIndex" + currentUserIndex, -1);
}
prefs.edit().putInt("userScaleIndex" + userId, userIndex).apply();
if (userIndex != -1) {
prefs.edit().putInt("userIdFromUserScaleIndex" + userIndex, userId).apply();
}
}
protected synchronized int getUserIdFromScaleIndex(int userScaleIndex) {
return prefs.getInt("userIdFromUserScaleIndex" + userScaleIndex, -1);
}
protected synchronized int getUserScaleIndex(int userId) {
return prefs.getInt("userScaleIndex" + userId, -1);
}
protected void reconnectOrSetSmState(SM_STEPS requestedState, SM_STEPS minState, Handler uiHandler) {
if (needReConnect()) {
jumpNextToStepNr(SM_STEPS.START.ordinal());
stopMachineState();
reConnectPreviousPeripheral(uiHandler);
return;
}
if (getStepNr() > minState.ordinal()) {
jumpNextToStepNr(requestedState.ordinal());
}
resumeMachineState();
}
@Override
public void selectScaleUserIndexForAppUserId(int appUserId, int scaleUserIndex, Handler uiHandler) {
Timber.d("Select scale user index from UI: user id: " + appUserId + ", scale user index: " + scaleUserIndex);
if (scaleUserIndex == -1) {
reconnectOrSetSmState(SM_STEPS.REGISTER_NEW_SCALE_USER, SM_STEPS.REGISTER_NEW_SCALE_USER, uiHandler);
}
else {
storeUserScaleIndex(appUserId, scaleUserIndex);
if (getUserScaleConsent(appUserId) == -1) {
enterScaleUserConsentUi(appUserId, scaleUserIndex);
}
else {
reconnectOrSetSmState(SM_STEPS.SELECT_SCALE_USER, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
}
}
}
@Override
public void setScaleUserConsent(int appUserId, int scaleUserConsent, Handler uiHandler) {
Timber.d("set scale user consent from UI: user id: " + appUserId + ", scale user consent: " + scaleUserConsent);
storeUserScaleConsentCode(appUserId, scaleUserConsent);
if (scaleUserConsent == -1) {
reconnectOrSetSmState(SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
}
else {
reconnectOrSetSmState(SM_STEPS.SELECT_SCALE_USER, SM_STEPS.REQUEST_VENDOR_SPECIFIC_USER_LIST, uiHandler);
}
}
protected void handleVendorSpecificUserList(byte[] value) {
Timber.d(String.format("Got user data: <%s>", byteInHex(value)));
BluetoothBytesParser parser = new BluetoothBytesParser(value);
int userListStatus = parser.getIntValue(FORMAT_UINT8);
if (userListStatus == 2) {
Timber.d("scale have no users!");
storeUserScaleConsentCode(selectedUser.getId(), -1);
storeUserScaleIndex(selectedUser.getId(), -1);
jumpNextToStepNr(SM_STEPS.REGISTER_NEW_SCALE_USER.ordinal());
resumeMachineState();
return;
}
else if (userListStatus == 1) {
for (int i = 0; i < scaleUserList.size(); i++) {
if (i == 0) {
Timber.d("scale user list:");
}
Timber.d("\n" + (i + 1) + ". " + scaleUserList.get(i));
}
if ((scaleUserList.size() == 0)) {
storeUserScaleConsentCode(selectedUser.getId(), -1);
storeUserScaleIndex(selectedUser.getId(), -1);
jumpNextToStepNr(SM_STEPS.REGISTER_NEW_SCALE_USER.ordinal());
resumeMachineState();
return;
}
if (getUserScaleIndex(selectedUser.getId()) == -1 || getUserScaleConsent(selectedUser.getId()) == -1) {
chooseExistingScaleUser(scaleUserList);
return;
}
resumeMachineState();
return;
}
int index = parser.getIntValue(FORMAT_UINT8);
String initials = parser.getStringValue();
int end = 3 > initials.length() ? initials.length() : 3;
initials = initials.substring(0, end);
if (initials.length() == 3) {
if (initials.charAt(0) == 0xff && initials.charAt(1) == 0xff && initials.charAt(2) == 0xff) {
initials = "";
}
}
parser.setOffset(5);
int year = parser.getIntValue(FORMAT_UINT16);
int month = parser.getIntValue(FORMAT_UINT8);
int day = parser.getIntValue(FORMAT_UINT8);
int height = parser.getIntValue(FORMAT_UINT8);
int gender = parser.getIntValue(FORMAT_UINT8);
int activityLevel = parser.getIntValue(FORMAT_UINT8);
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
ScaleUser scaleUser = new ScaleUser();
scaleUser.setUserName(initials);
scaleUser.setBirthday(calendar.getTime());
scaleUser.setBodyHeight(height);
scaleUser.setGender(Converters.Gender.fromInt(gender));
scaleUser.setActivityLevel(Converters.ActivityLevel.fromInt(activityLevel - 1));
scaleUser.setId(index);
scaleUserList.add(scaleUser);
if (scaleUserList.size() == getVendorSpecificMaxUserCount()) {
if (getUserScaleIndex(selectedUser.getId()) == -1 || getUserScaleConsent(selectedUser.getId()) == -1) {
chooseExistingScaleUser(scaleUserList);
return;
}
resumeMachineState();
}
}
protected void chooseExistingScaleUser(Vector<ScaleUser> userList) {
final DateFormat dateFormat = DateFormat.getDateInstance();
int choicesCount = userList.size();
if (userList.size() < getVendorSpecificMaxUserCount()) {
choicesCount = userList.size() + 1;
}
CharSequence[] choiceStrings = new String[choicesCount];
int indexArray[] = new int[choicesCount];
int selectedItem = -1;
for (int i = 0; i < userList.size(); ++i) {
ScaleUser u = userList.get(i);
String name = u.getUserName();
choiceStrings[i] = (name.length() > 0 ? name : String.format("P%02d", u.getId()))
+ " " + context.getString(u.getGender().isMale() ? R.string.label_male : R.string.label_female).toLowerCase()
+ " " + context.getString(R.string.label_height).toLowerCase() + ":" + u.getBodyHeight()
+ " " + context.getString(R.string.label_birthday).toLowerCase() + ":" + dateFormat.format(u.getBirthday())
+ " " + context.getString(R.string.label_activity_level).toLowerCase() + ":" + (u.getActivityLevel().toInt() + 1);
indexArray[i] = u.getId();
}
if (userList.size() < getVendorSpecificMaxUserCount()) {
choiceStrings[userList.size()] = context.getString(R.string.info_create_new_user_on_scale);
indexArray[userList.size()] = -1;
}
Pair<CharSequence[], int[]> choices = new Pair(choiceStrings, indexArray);
chooseScaleUserUi(choices);
}
protected String getInitials(String fullName) {
if (fullName == null || fullName.isEmpty() || fullName.chars().allMatch(Character::isWhitespace)) {
return getDefaultInitials();
}
return buildInitialsStringFrom(fullName).toUpperCase();
}
private String getDefaultInitials() {
int userId = this.selectedUser.getId();
int userIndex = getUserScaleIndex(userId);
return "P" + userIndex + " ";
}
private String buildInitialsStringFrom(String fullName) {
String[] name = fullName.trim().split(" +");
String initials = "";
for (int i = 0; i < 3; i++) {
if (i < name.length && name[i] != "") {
initials += name[i].charAt(0);
} else {
initials += " ";
}
}
return initials;
}
}

View File

@@ -44,17 +44,21 @@ public class MiScaleLib {
return weight / (((height * height) / 100.0f) / 100.0f);
}
public float getLBM(float weight, float impedance) {
float leanBodyMass = weight - ((getBodyFat(weight, impedance) * 0.01f) * weight) - getBoneMass(weight, impedance);
if (sex == 0 && leanBodyMass >= 84.0f) {
leanBodyMass = 120.0f;
}
else if (sex == 1 && leanBodyMass >= 93.5f) {
leanBodyMass = 120.0f;
}
return leanBodyMass;
}
public float getMuscle(float weight, float impedance) {
float muscleMass = weight - ((getBodyFat(weight, impedance) * 0.01f) * weight) - getBoneMass(weight, impedance);
if (sex == 0 && muscleMass >= 84.0f) {
muscleMass = 120.0f;
}
else if (sex == 1 && muscleMass >= 93.5f) {
muscleMass = 120.0f;
}
return muscleMass;
return this.getLBM(weight,impedance); // this is wrong but coherent with MiFit app behaviour
}
public float getWater(float weight, float impedance) {

View File

@@ -33,8 +33,12 @@ public class OneByoneLib {
return weight / (((height * height) / 100.0f) / 100.0f);
}
public float getMuscle(float weight, float bodyFat, float boneMass) {
return (weight - ((bodyFat / 100.0f) * weight)) - boneMass;
public float getLBM(float weight, float bodyFat) {
return weight - (bodyFat / 100.0f * weight);
}
public float getMuscle(float weight, float impedanceValue){
return (float)((height * height / impedanceValue * 0.401) + (sex * 3.825) - (age * 0.071) + 5.102) / weight * 100.0f;
}
public float getWater(float bodyFat) {
@@ -50,7 +54,7 @@ public class OneByoneLib {
return coeff * water;
}
public float getBoneMass(float weight, int impedanceValue) {
public float getBoneMass(float weight, float impedanceValue) {
float boneMass, sexConst , peopleCoeff = 0.0f;
switch (peopleType) {
@@ -173,30 +177,12 @@ public class OneByoneLib {
}
}
public float getBodyFat(float weight, int impedanceCoeff) {
float impedanceValue, bodyFatConst=0;
public float getBodyFat(float weight, float impedanceValue) {
float bodyFatConst=0;
if (impedanceCoeff == 0x0FFFFFF) {
return 1.0f;
}
impedanceValue = (float)(((impedanceCoeff & 0x0FF0000) >> 0x10) + (impedanceCoeff & 0x0F00) - ((impedanceCoeff & 0x0F000) >> 0xC) + (0 - (impedanceCoeff & 0xFF)) * 4) * 0.5f;
if (50.0f <= impedanceValue) {
if (impedanceValue > 3000.0f) {
bodyFatConst = 0.0068f;
} else {
if (200.0f > impedanceValue) {
bodyFatConst = 1.36f;
} else {
if (impedanceValue > 1200) {
bodyFatConst = 8.16f;
} else {
bodyFatConst = 0.0068f * impedanceValue;
}
}
}
}
if (impedanceValue >= 1200.0f) bodyFatConst = 8.16f;
else if (impedanceValue >= 200.0f) bodyFatConst = 0.0068f * impedanceValue;
else if (impedanceValue >= 50.0f) bodyFatConst = 1.36f;
float peopleTypeCoeff, bodyVar, bodyFat;
@@ -236,19 +222,19 @@ public class OneByoneLib {
if (sex != 0) {
if (61.0f > weight) {
bodyVar = 0.98f;
bodyVar *= 0.98f;
}
} else {
if (50.0f > weight) {
bodyVar = 1.02f;
bodyVar *= 1.02f;
}
if (weight > 60.0f) {
bodyVar = 0.96f;
bodyVar *= 0.96f;
}
if (height > 160.0f) {
bodyVar = 1.03f;
bodyVar *= 1.03f;
}
}

View File

@@ -0,0 +1,201 @@
package com.health.openscale.core.bluetooth.lib;
// This class is similar to OneByoneLib, but the way measures are computer are slightly different
public class OneByoneNewLib {
private int sex;
private int age;
private float height;
private int peopleType; // low activity = 0; medium activity = 1; high activity = 2
public OneByoneNewLib(int sex, int age, float height, int peopleType) {
this.sex = sex;
this.age = age;
this.height = height;
this.peopleType = peopleType;
}
public float getBMI(float weight) {
float bmi = weight / (((height * height) / 100.0f) / 100.0f);
return getBounded(bmi, 10, 90);
}
public float getLBM(float weight, int impedance) {
float lbmCoeff = height / 100 * height / 100 * 9.058F;
lbmCoeff += 12.226;
lbmCoeff += weight * 0.32;
lbmCoeff -= impedance * 0.0068;
lbmCoeff -= age * 0.0542;
return lbmCoeff;
}
public float getBMMRCoeff(float weight){
int bmmrCoeff = 20;
if(sex == 1){
bmmrCoeff = 21;
if(age < 0xd){
bmmrCoeff = 36;
} else if(age < 0x10){
bmmrCoeff = 30;
} else if(age < 0x12){
bmmrCoeff = 26;
} else if(age < 0x1e){
bmmrCoeff = 23;
} else if (age >= 0x32){
bmmrCoeff = 20;
}
} else {
if(age < 0xd){
bmmrCoeff = 34;
} else if(age < 0x10){
bmmrCoeff = 29;
} else if(age < 0x12){
bmmrCoeff = 24;
} else if(age < 0x1e){
bmmrCoeff = 22;
} else if (age >= 0x32){
bmmrCoeff = 19;
}
}
return bmmrCoeff;
}
public float getBMMR(float weight){
float bmmr;
if(sex == 1){
bmmr = (weight * 14.916F + 877.8F) - height * 0.726F;
bmmr -= age * 8.976;
} else {
bmmr = (weight * 10.2036F + 864.6F) - height * 0.39336F;
bmmr -= age * 6.204;
}
return getBounded(bmmr, 500, 1000);
}
public float getBodyFatPercentage(float weight, int impedance) {
float bodyFat = getLBM(weight, impedance);
float bodyFatConst;
if (sex == 0) {
if (age < 0x32) {
bodyFatConst = 9.25F;
} else {
bodyFatConst = 7.25F;
}
} else {
bodyFatConst = 0.8F;
}
bodyFat -= bodyFatConst;
if (sex == 0){
if (weight < 50){
bodyFat *= 1.02;
} else if(weight > 60){
bodyFat *= 0.96;
}
if(height > 160){
bodyFat *= 1.03;
}
} else {
if (weight < 61){
bodyFat *= 0.98;
}
}
return 100 * (1 - bodyFat / weight);
}
public float getBoneMass(float weight, int impedance){
float lbmCoeff = getLBM(weight, impedance);
float boneMassConst;
if(sex == 1){
boneMassConst = 0.18016894F;
} else {
boneMassConst = 0.245691014F;
}
boneMassConst = lbmCoeff * 0.05158F - boneMassConst;
float boneMass;
if(boneMassConst <= 2.2){
boneMass = boneMassConst - 0.1F;
} else {
boneMass = boneMassConst + 0.1F;
}
return getBounded(boneMass, 0.5F, 8);
}
public float getMuscleMass(float weight, int impedance){
float muscleMass = weight - getBodyFatPercentage(weight, impedance) * 0.01F * weight;
muscleMass -= getBoneMass(weight, impedance);
return getBounded(muscleMass, 10, 120);
}
public float getSkeletonMusclePercentage(float weight, int impedance){
float skeletonMuscleMass = getWaterPercentage(weight, impedance);
skeletonMuscleMass *= weight;
skeletonMuscleMass *= 0.8422F * 0.01F;
skeletonMuscleMass -= 2.9903;
skeletonMuscleMass /= weight;
return skeletonMuscleMass * 100;
}
public float getVisceralFat(float weight){
float visceralFat;
if (sex == 1) {
if (height < weight * 1.6 + 63.0) {
visceralFat =
age * 0.15F + ((weight * 305.0F) /((height * 0.0826F * height - height * 0.4F) + 48.0F) - 2.9F);
}
else {
visceralFat = age * 0.15F + (weight * (height * -0.0015F + 0.765F) - height * 0.143F) - 5.0F;
}
}
else {
if (weight <= height * 0.5 - 13.0) {
visceralFat = age * 0.07F + (weight * (height * -0.0024F + 0.691F) - height * 0.027F) - 10.5F;
}
else {
visceralFat = age * 0.07F + ((weight * 500.0F) / ((height * 1.45F + height * 0.1158F * height) - 120.0F) - 6.0F);
}
}
return getBounded(visceralFat, 1, 50);
}
public float getWaterPercentage(float weight, int impedance){
float waterPercentage = (100 - getBodyFatPercentage(weight, impedance)) * 0.7F;
if (waterPercentage > 50){
waterPercentage *= 0.98;
} else {
waterPercentage *= 1.02;
}
return getBounded(waterPercentage, 35, 75);
}
public float getProteinPercentage(float weight, int impedance){
return (
(100.0F - getBodyFatPercentage(weight, impedance))
- getWaterPercentage(weight, impedance) * 1.08F
)
- (getBoneMass(weight, impedance) / weight) * 100.0F;
}
private float getBounded(float value, float lowerBound, float upperBound){
if(value < lowerBound){
return lowerBound;
} else if (value > upperBound){
return upperBound;
}
return value;
}
}

View File

@@ -488,6 +488,10 @@ public class ScaleMeasurement implements Cloneable {
float k0, k1, k2, ka;
if (caliper1 == 0.0f || caliper2 == 0.0f || caliper3 == 0.0f){
return 0.0f;
}
float s = (caliper1 + caliper2 + caliper3) * 10.0f; // cm to mm
if (scaleUser.getGender().isMale()) {

View File

@@ -39,6 +39,9 @@ public class EvaluationSheet {
private List<sheetEntry> bmiEvaluateSheet_Man;
private List<sheetEntry> bmiEvaluateSheet_Woman;
private List<sheetEntry> lbmEvaluateSheet_Man;
private List<sheetEntry> lbmEvaluateSheet_Woman;
private List<sheetEntry> waistEvaluateSheet_Man;
private List<sheetEntry> waistEvaluateSheet_Woman;
@@ -91,6 +94,9 @@ public class EvaluationSheet {
visceralFatEvaluateSheet = new ArrayList<>();
lbmEvaluateSheet_Man = new ArrayList<>();
lbmEvaluateSheet_Woman = new ArrayList<>();
initEvaluationSheets();
}
@@ -119,25 +125,20 @@ public class EvaluationSheet {
waterEvaluateSheet_Woman.add(new sheetEntry(10, 1000, 45, 60));
muscleEvaluateSheet_Man.add(new sheetEntry(10, 14, 44, 57));
muscleEvaluateSheet_Man.add(new sheetEntry(15, 19, 43, 56));
muscleEvaluateSheet_Man.add(new sheetEntry(20, 29, 42, 54));
muscleEvaluateSheet_Man.add(new sheetEntry(30, 39, 41, 52));
muscleEvaluateSheet_Man.add(new sheetEntry(40, 49, 40, 50));
muscleEvaluateSheet_Man.add(new sheetEntry(50, 59, 39, 48));
muscleEvaluateSheet_Man.add(new sheetEntry(60, 69, 38, 47));
muscleEvaluateSheet_Man.add(new sheetEntry(70, 1000, 37, 46));
muscleEvaluateSheet_Woman.add(new sheetEntry(10, 14, 36, 43));
muscleEvaluateSheet_Woman.add(new sheetEntry(15, 19, 35, 41));
muscleEvaluateSheet_Woman.add(new sheetEntry(20, 29, 34, 39));
muscleEvaluateSheet_Woman.add(new sheetEntry(30, 39, 33, 38));
muscleEvaluateSheet_Woman.add(new sheetEntry(40, 49, 31, 36));
muscleEvaluateSheet_Woman.add(new sheetEntry(50, 59, 29, 34));
muscleEvaluateSheet_Woman.add(new sheetEntry(60, 69, 28, 33));
muscleEvaluateSheet_Woman.add(new sheetEntry(70, 1000, 27, 32));
// Muscle Reference: "Skeletal muscle mass and distribution in 468 men and women aged 1888 yr" by IAN JANSSEN, STEVEN B. HEYMSFIELD, ZIMIAN WANG, and ROBERT ROS in J Appl Physiol89: 8188, 2000
muscleEvaluateSheet_Man.add(new sheetEntry(18, 29, 37.9f, 46.7f));
muscleEvaluateSheet_Man.add(new sheetEntry(30, 39, 34.1f, 44.1f));
muscleEvaluateSheet_Man.add(new sheetEntry(40, 49, 33.1f, 41.1f));
muscleEvaluateSheet_Man.add(new sheetEntry(50, 59, 31.7f, 38.5f));
muscleEvaluateSheet_Man.add(new sheetEntry(60, 69, 29.9f, 37.7f));
muscleEvaluateSheet_Man.add(new sheetEntry(70, 1000, 28.7f, 43.3f));
muscleEvaluateSheet_Woman.add(new sheetEntry(18, 29, 28.4f, 39.8f));
muscleEvaluateSheet_Woman.add(new sheetEntry(30, 39, 25.0f, 36.2f));
muscleEvaluateSheet_Woman.add(new sheetEntry(40, 49, 24.2f, 34.2f));
muscleEvaluateSheet_Woman.add(new sheetEntry(50, 59, 24.7f, 33.5f));
muscleEvaluateSheet_Woman.add(new sheetEntry(60, 69, 22.7f, 31.9f));
muscleEvaluateSheet_Woman.add(new sheetEntry(70, 1000, 25.5f, 34.9f));
bmiEvaluateSheet_Man.add(new sheetEntry(16, 24, 20, 25));
bmiEvaluateSheet_Man.add(new sheetEntry(25, 34, 21, 26));
@@ -146,7 +147,6 @@ public class EvaluationSheet {
bmiEvaluateSheet_Man.add(new sheetEntry(55, 64, 24, 29));
bmiEvaluateSheet_Man.add(new sheetEntry(65, 90, 25, 30));
bmiEvaluateSheet_Woman.add(new sheetEntry(16, 24, 19, 24));
bmiEvaluateSheet_Woman.add(new sheetEntry(25, 34, 20, 25));
bmiEvaluateSheet_Woman.add(new sheetEntry(35, 44, 21, 26));
@@ -169,6 +169,22 @@ public class EvaluationSheet {
whrEvaluateSheet_Woman.add(new sheetEntry(18, 90, 0.7f, 0.8f));
visceralFatEvaluateSheet.add(new sheetEntry(18, 90, -1, 12));
// Lean body mass reference: "Lean body mass: reference values for Italian population between 18 to 88 years old" DOI: 10.26355/eurrev_201811_16415
// assuming low limits as P25 and upper limit as P75
lbmEvaluateSheet_Man.add(new sheetEntry(18, 24, 52.90f, 62.70f));
lbmEvaluateSheet_Man.add(new sheetEntry(25, 34, 53.10f, 64.80f));
lbmEvaluateSheet_Man.add(new sheetEntry(35, 44, 53.83f, 65.60f));
lbmEvaluateSheet_Man.add(new sheetEntry(45, 54, 53.60f, 65.20f));
lbmEvaluateSheet_Man.add(new sheetEntry(55, 64, 51.63f, 61.10f));
lbmEvaluateSheet_Man.add(new sheetEntry(65, 74, 48.48f, 58.20f));
lbmEvaluateSheet_Man.add(new sheetEntry(75, 88, 43.35f, 60.23f));
lbmEvaluateSheet_Woman.add(new sheetEntry(18, 24, 34.30f, 41.90f));
lbmEvaluateSheet_Woman.add(new sheetEntry(25, 34, 35.20f, 43.70f));
lbmEvaluateSheet_Woman.add(new sheetEntry(35, 44, 35.60f, 47.10f));
lbmEvaluateSheet_Woman.add(new sheetEntry(45, 54, 36.10f, 44.90f));
lbmEvaluateSheet_Woman.add(new sheetEntry(55, 64, 35.15f, 43.95f));
lbmEvaluateSheet_Woman.add(new sheetEntry(65, 74, 34.10f, 42.05f));
lbmEvaluateSheet_Woman.add(new sheetEntry(75, 88, 33.80f, 40.40f));
}
@@ -245,6 +261,18 @@ public class EvaluationSheet {
return evaluateSheet(bmi, bodyEvaluateSheet);
}
public EvaluationResult evaluateLBM(float lbm) {
List<sheetEntry> bodyEvaluateSheet;
if (evalUser.getGender().isMale()) {
bodyEvaluateSheet = lbmEvaluateSheet_Man;
} else {
bodyEvaluateSheet = lbmEvaluateSheet_Woman;
}
return evaluateSheet(lbm, bodyEvaluateSheet);
}
public EvaluationResult evaluateWaist(float waist) {
List<sheetEntry> bodyEvaluateSheet;

View File

@@ -16,7 +16,9 @@
package com.health.openscale.gui;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.ActivityNotFoundException;
@@ -24,29 +26,41 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.Editable;
import android.text.Html;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -91,7 +105,6 @@ public class MainActivity extends AppCompatActivity
private static final int IMPORT_DATA_REQUEST = 100;
private static final int EXPORT_DATA_REQUEST = 101;
private static final int ENABLE_BLUETOOTH_REQUEST = 102;
private static final int APPINTRO_REQUEST = 103;
private AppBarConfiguration mAppBarConfiguration;
@@ -475,6 +488,11 @@ public class MainActivity extends AppCompatActivity
builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (userIds.isEmpty()) {
Toast.makeText(getApplicationContext(), getString(R.string.info_assisted_weighing_no_reference_user), Toast.LENGTH_LONG).show();
return;
}
int selectedPosition = ((AlertDialog)dialog).getListView().getCheckedItemPosition();
prefs.edit().putInt("assistedWeighingRefUserId", userIds.get(selectedPosition)).commit();
@@ -518,7 +536,6 @@ public class MainActivity extends AppCompatActivity
boolean hasBluetooth = bluetoothManager.getAdapter() != null;
if (!hasBluetooth) {
bluetoothStatus.setEnabled(false);
setBluetoothStatusIcon(R.drawable.ic_bluetooth_disabled);
}
// Just search for a bluetooth device just once at the start of the app and if start preference enabled
@@ -557,6 +574,54 @@ public class MainActivity extends AppCompatActivity
return;
}
Timber.d("Main Activity Bluetooth permission check");
int targetSdkVersion = getApplicationInfo().targetSdkVersion;
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
// Check if Bluetooth is enabled
if (btAdapter == null || !btAdapter.isEnabled()) {
Timber.d("Bluetooth is not enabled");
Toast.makeText(this, "Bluetooth " + getResources().getString(R.string.info_is_not_enable), Toast.LENGTH_SHORT).show();
setBluetoothStatusIcon(R.drawable.ic_bluetooth_disabled);
return;
}
// Check if Bluetooth 4.x is available
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Timber.d("No Bluetooth 4.x available");
Toast.makeText(this, "Bluetooth 4.x " + getResources().getString(R.string.info_is_not_available), Toast.LENGTH_SHORT).show();
setBluetoothStatusIcon(R.drawable.ic_bluetooth_disabled);
return;
}
// Check if GPS or Network location service is enabled
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (!(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
Timber.d("No GPS or Network location service is enabled, ask user for permission");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.permission_bluetooth_info_title);
builder.setIcon(R.drawable.ic_preferences_about);
builder.setMessage(R.string.permission_location_service_info);
builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
// Show location settings when the user acknowledges the alert dialog
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
setBluetoothStatusIcon(R.drawable.ic_bluetooth_disabled);
return;
}
String deviceName = prefs.getString(
BluetoothSettingsFragment.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, "");
String hwAddress = prefs.getString(
@@ -568,18 +633,70 @@ public class MainActivity extends AppCompatActivity
return;
}
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
if (!bluetoothManager.getAdapter().isEnabled()) {
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST);
return;
String[] requiredPermissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && targetSdkVersion >= Build.VERSION_CODES.S) {
Timber.d("SDK >= 31 request for Bluetooth Scan and Bluetooth connect permissions");
requiredPermissions = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && targetSdkVersion >= Build.VERSION_CODES.Q) {
Timber.d("SDK >= 29 request for Access fine location permission");
requiredPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
} else {
Timber.d("SDK < 29 request for coarse location permission");
requiredPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
}
if (hasPermissions(requiredPermissions)) {
connectToBluetooth();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
Timber.d("No access fine location permission granted");
builder.setMessage(R.string.permission_bluetooth_info)
.setTitle(R.string.permission_bluetooth_info_title)
.setIcon(R.drawable.ic_preferences_about)
.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
Timber.d("No access Bluetooth scan permission granted");
builder.setMessage(R.string.permission_bluetooth_info)
.setTitle(R.string.permission_bluetooth_info_title)
.setIcon(R.drawable.ic_preferences_about)
.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
} else {
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
}
private void connectToBluetooth() {
String deviceName = prefs.getString(
BluetoothSettingsFragment.PREFERENCE_KEY_BLUETOOTH_DEVICE_NAME, "");
String hwAddress = prefs.getString(
BluetoothSettingsFragment.PREFERENCE_KEY_BLUETOOTH_HW_ADDRESS, "");
Toast.makeText(getApplicationContext(), getResources().getString(R.string.info_bluetooth_try_connection) + " " + deviceName, Toast.LENGTH_SHORT).show();
setBluetoothStatusIcon(R.drawable.ic_bluetooth_searching);
if (!openScale.connectToBluetoothDevice(deviceName, hwAddress, callbackBtHandler)) {
if (!OpenScale.getInstance().connectToBluetoothDevice(deviceName, hwAddress, callbackBtHandler)) {
setBluetoothStatusIcon(R.drawable.ic_bluetooth_connection_lost);
Toast.makeText(getApplicationContext(), deviceName + " " + getResources().getString(R.string.label_bt_device_no_support), Toast.LENGTH_SHORT).show();
}
@@ -651,10 +768,92 @@ public class MainActivity extends AppCompatActivity
Timber.e("Bluetooth scale message error: " + ex);
}
break;
case CHOOSE_SCALE_USER:
chooseScaleUser(msg);
break;
case ENTER_SCALE_USER_CONSENT:
enterScaleUserConsent(msg);
break;
}
}
};
private void chooseScaleUser(Message msg) {
AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
Pair<CharSequence[], int[]> choices = (Pair<CharSequence[], int[]>)msg.obj;
mBuilder.setTitle(getResources().getString(R.string.info_select_scale_user));
mBuilder.setSingleChoiceItems(choices.first, -1 , new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
Timber.d("UI selected " + i + ": " + choices.first[i] + " P-0" + choices.second[i]);
OpenScale.getInstance().setBluetoothDeviceUserIndex(OpenScale.getInstance().getSelectedScaleUser().getId(), choices.second[i], callbackBtHandler);
dialoginterface.dismiss();
}
});
mBuilder.setNegativeButton(getResources().getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
dialoginterface.dismiss();
}
});
AlertDialog mDialog = mBuilder.create();
mDialog.show();
}
private void enterScaleUserConsent(Message msg) {
final int appUserId = msg.arg1;
final int scaleUserIndex = msg.arg2;
final int[] consentCode = {-1};
AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
mBuilder.setTitle(getResources().getString(R.string.info_enter_consent_code_for_scale_user, Integer.toString(scaleUserIndex)));
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_NUMBER);
InputFilter[] filterArray = new InputFilter[1];
filterArray[0] = new InputFilter.LengthFilter(4);
input.setFilters(filterArray);
mBuilder.setView(input);
mBuilder.setPositiveButton(getResources().getString(R.string.label_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialoginterface, int i) {
OpenScale.getInstance().setBluetoothDeviceUserConsent(appUserId, consentCode[0], callbackBtHandler);
dialoginterface.dismiss();
}
});
mBuilder.setNegativeButton(getResources().getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialoginterface, int i) {
OpenScale.getInstance().setBluetoothDeviceUserConsent(appUserId, -1, callbackBtHandler);
dialoginterface.dismiss();
}
});
AlertDialog mDialog = mBuilder.create();
mDialog.show();
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
input.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
try {
consentCode[0] = Integer.parseInt(s.toString());
Timber.d("consent code set to " + consentCode[0] + "(" + s.toString() + ")");
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
} catch(NumberFormatException nfe) {
Timber.d("Could not parse " + nfe);
mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
}
});
}
private void setBluetoothStatusIcon(int iconResource) {
bluetoothStatusIcon = iconResource;
bluetoothStatus.setIcon(getResources().getDrawable(bluetoothStatusIcon));
@@ -795,16 +994,6 @@ public class MainActivity extends AppCompatActivity
OpenScale openScale = OpenScale.getInstance();
if (requestCode == ENABLE_BLUETOOTH_REQUEST) {
if (resultCode == RESULT_OK) {
invokeConnectToBluetoothDevice();
}
else {
Toast.makeText(this, "Bluetooth " + getResources().getString(R.string.info_is_not_enable), Toast.LENGTH_SHORT).show();
}
return;
}
if (requestCode == APPINTRO_REQUEST) {
if (openScale.getSelectedScaleUserId() == -1) {
MobileNavigationDirections.ActionNavMobileNavigationToNavUsersettings action = MobileNavigationDirections.actionNavMobileNavigationToNavUsersettings();
@@ -855,4 +1044,29 @@ public class MainActivity extends AppCompatActivity
break;
}
}
private boolean hasPermissions(String[] permissions) {
if (permissions != null) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
Timber.d("Permission is not granted: " + permission);
return false;
}
Timber.d("Permission already granted: " + permission);
}
return true;
}
return false;
}
private ActivityResultLauncher<String[]> requestPermissionBluetoothLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
if (isGranted.containsValue(false)) {
Timber.d("At least one Bluetooth permission was not granted");
Toast.makeText(this, getString(R.string.label_bluetooth_title) + ": " + getString(R.string.permission_not_granted), Toast.LENGTH_SHORT).show();
}
else {
connectToBluetooth();
}
});
}

View File

@@ -58,7 +58,7 @@ public class BMIMeasurementView extends FloatMeasurementView {
@Override
protected float getMaxValue() {
return 50;
return 125;
}
@Override

View File

@@ -352,6 +352,8 @@ public class ChartMeasurementView extends LineChart {
}
public void refreshMeasurementList() {
highlightValue(null, false); // deselect any highlighted value
if (scaleMeasurementList == null) {
progressBar.setVisibility(GONE);
return;
@@ -370,7 +372,7 @@ public class ChartMeasurementView extends LineChart {
for (int i=0; i<scaleMeasurementList.size(); i++) {
ScaleMeasurement measurement = scaleMeasurementList.get(i);
float value = measurementView.getMeasurementValue(measurement);
float value = measurementView.getConvertedMeasurementValue(measurement);
if (value == 0.0f) {
continue;
@@ -392,12 +394,16 @@ public class ChartMeasurementView extends LineChart {
}
}
if (prefs.getBoolean("trendLine", true)) {
if (prefs.getBoolean("trendLine", false)) {
addTrendLine(lineDataSets);
}
LineData data = new LineData(lineDataSets);
setData(data);
if (!lineDataSets.isEmpty()) {
LineData data = new LineData(lineDataSets);
setData(data);
} else {
setData(null);
}
if (prefs.getBoolean("goalLine", false)) {
addGoalLine(lineDataSets);
@@ -423,12 +429,12 @@ public class ChartMeasurementView extends LineChart {
measurementLine.setDrawCircles(prefs.getBoolean("pointsEnable", true));
measurementLine.setDrawValues(prefs.getBoolean("labelsEnable", false));
measurementLine.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
if (prefs.getBoolean("trendLine", true)) {
if (prefs.getBoolean("trendLine", false)) {
// show only data point if trend line is enabled
measurementLine.enableDashedLine(0, 1, 0);
}
if (measurementView.isVisible()) {
if (measurementView.isVisible() && !lineEntries.isEmpty()) {
if (isInGraphKey) {
if (measurementView.getSettings().isInGraph()) {
lineDataSets.add(measurementLine);
@@ -484,21 +490,35 @@ public class ChartMeasurementView extends LineChart {
private void addTrendLine(List<ILineDataSet> lineDataSets) {
List<ScaleMeasurement> scaleMeasurementsAsTrendlineList = getScaleMeasurementsAsTrendline(scaleMeasurementList);
for (MeasurementView view : measurementViews) {
if (view instanceof FloatMeasurementView && view.isVisible()) {
final FloatMeasurementView measurementView = (FloatMeasurementView) view;
final List<Entry> lineEntries = new ArrayList<>();
for (int i=0; i<scaleMeasurementsAsTrendlineList.size(); i++) {
ScaleMeasurement measurement = scaleMeasurementsAsTrendlineList.get(i);
ArrayList<ScaleMeasurement> nonZeroScaleMeasurementList = new ArrayList<>();
// filter first all zero measurements out, so that the follow-up trendline calculations are not based on them
for (int i=0; i<scaleMeasurementList.size(); i++) {
ScaleMeasurement measurement = scaleMeasurementList.get(i);
float value = measurementView.getMeasurementValue(measurement);
if (value == 0.0f) {
continue;
if (value != 0.0f) {
nonZeroScaleMeasurementList.add(measurement);
}
}
// check if we have some data left otherwise skip the measurement
if (nonZeroScaleMeasurementList.isEmpty()) {
continue;
}
// calculate the trendline from the non-zero scale measurement list
List<ScaleMeasurement> scaleMeasurementsAsTrendlineList = getScaleMeasurementsAsTrendline(nonZeroScaleMeasurementList);
for (int i=0; i<scaleMeasurementsAsTrendlineList.size(); i++) {
ScaleMeasurement measurement = scaleMeasurementsAsTrendlineList.get(i);
float value = measurementView.getConvertedMeasurementValue(measurement);
Entry entry = new Entry();
entry.setX(convertDateToInt(measurement.getDateTime()));
@@ -523,22 +543,30 @@ public class ChartMeasurementView extends LineChart {
return;
}
PolynomialFitter polyFitter = new PolynomialFitter(3);
PolynomialFitter polyFitter = new PolynomialFitter(lineEntries.size() == 2 ? 2 : 3);
// add last point to polynomial fitter first
int lastPos = lineEntries.size() - 1;
Entry lastEntry = lineEntries.get(lastPos);
polyFitter.addPoint((double) lastEntry.getX(), (double) lastEntry.getY());
// use only the last 30 values for the polynomial fitter
for (int i=1; i<30; i++) {
for (int i=2; i<30; i++) {
int pos = lineEntries.size() - i;
if (pos >= 0) {
Entry entry = lineEntries.get(pos);
Entry prevEntry = lineEntries.get(pos+1);
polyFitter.addPoint((double) entry.getX(), (double) entry.getY());
// check if x position is different otherwise that point is useless for the polynomial calculation.
if (entry.getX() != prevEntry.getX()) {
polyFitter.addPoint((double) entry.getX(), (double) entry.getY());
}
}
}
PolynomialFitter.Polynomial polynomial = polyFitter.getBestFit();
Entry lastEntry = lineEntries.get(lineEntries.size() - 1);
int maxX = (int) lastEntry.getX()+1;
List<Entry> predictionValues = new Stack<>();

View File

@@ -241,6 +241,17 @@ public abstract class FloatMeasurementView extends MeasurementView {
protected abstract float getMeasurementValue(ScaleMeasurement measurement);
protected abstract void setMeasurementValue(float value, ScaleMeasurement measurement);
public float getConvertedMeasurementValue(ScaleMeasurement measurement) {
updateUserConvertedWeight(measurement);
float convertedValue = getMeasurementValue(measurement);
convertedValue = maybeConvertValue(convertedValue);
convertedValue = clampValue(convertedValue);
convertedValue = roundValue(convertedValue);
return convertedValue;
}
public abstract String getUnit();
protected abstract float getMaxValue();
protected int getDecimalPlaces() {
@@ -350,21 +361,12 @@ public abstract class FloatMeasurementView extends MeasurementView {
float newPreviousValue = NO_VALUE;
if (!useAutoValue()) {
updateUserConvertedWeight(measurement);
newValue = getMeasurementValue(measurement);
newValue = maybeConvertValue(newValue);
newValue = clampValue(newValue);
newValue = roundValue(newValue);
newValue = getConvertedMeasurementValue(measurement);
if (previousMeasurement != null) {
float saveUserConvertedWeight = userConvertedWeight;
updateUserConvertedWeight(previousMeasurement);
newPreviousValue = getMeasurementValue(previousMeasurement);
newPreviousValue = maybeConvertValue(newPreviousValue);
newPreviousValue = clampValue(newPreviousValue);
newPreviousValue = roundValue(newPreviousValue);
newPreviousValue = getConvertedMeasurementValue(previousMeasurement);
userConvertedWeight = saveUserConvertedWeight;
}

View File

@@ -86,6 +86,6 @@ public class LBMMeasurementView extends FloatMeasurementView {
@Override
protected EvaluationResult evaluateSheet(EvaluationSheet evalSheet, float value) {
return null;
return evalSheet.evaluateLBM(value);
}
}

View File

@@ -15,16 +15,23 @@
*/
package com.health.openscale.gui.preferences;
import static android.app.Activity.RESULT_OK;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
@@ -34,11 +41,10 @@ import com.health.openscale.R;
import com.health.openscale.core.OpenScale;
import com.health.openscale.core.alarm.AlarmBackupHandler;
import com.health.openscale.core.alarm.ReminderBootReceiver;
import com.health.openscale.gui.utils.PermissionHelper;
import java.io.IOException;
import static android.app.Activity.RESULT_OK;
import timber.log.Timber;
public class BackupPreferences extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String PREFERENCE_KEY_IMPORT_BACKUP = "importBackup";
@@ -86,11 +92,13 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
isAutoBackupAskForPermission = false;
if (autoBackup.isChecked()) {
Timber.d("Auto-Backup enabled");
alarmBackupHandler.scheduleAlarms(getActivity());
pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
} else {
Timber.d("Auto-Backup disabled");
alarmBackupHandler.disableAlarm(getActivity());
pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
@@ -122,12 +130,25 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
private class onClickListenerAutoBackup implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (autoBackup.isChecked()) {
isAutoBackupAskForPermission = true;
PermissionHelper.requestWritePermission(fragment);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (autoBackup.isChecked()) {
autoBackup.setChecked(true);
} else {
autoBackup.setChecked(false);
}
}
else {
if (autoBackup.isChecked()) {
isAutoBackupAskForPermission = true;
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
exportBackup();
} else {
requestPermissionExportLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
}
return true;
}
}
@@ -135,10 +156,16 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
private class onClickListenerImportBackup implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (PermissionHelper.requestReadPermission(fragment)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
importBackup();
} else {
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
importBackup();
} else {
requestPermissionImportLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
return true;
}
}
@@ -146,8 +173,15 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
private class onClickListenerExportBackup implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (PermissionHelper.requestWritePermission(fragment)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
exportBackup();
} else {
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
exportBackup();
} else {
requestPermissionExportLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
return true;
@@ -208,6 +242,7 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_TITLE, "openScale.db");
intent.setType("*/*");
startActivityForResult(intent, EXPORT_DATA_REQUEST);
@@ -216,36 +251,35 @@ public class BackupPreferences extends PreferenceFragmentCompat implements Share
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_READ_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
}
private ActivityResultLauncher<String> requestPermissionImportLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
importBackup();
} else {
}
else {
Toast.makeText(getContext(), getResources().getString(R.string.permission_not_granted), Toast.LENGTH_SHORT).show();
}
break;
case PermissionHelper.PERMISSIONS_REQUEST_ACCESS_WRITE_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
});
private ActivityResultLauncher<String> requestPermissionExportLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
if (isAutoBackupAskForPermission) {
autoBackup.setChecked(true);
} else {
exportBackup();
}
} else {
}
else {
if (isAutoBackupAskForPermission) {
autoBackup.setChecked(false);
}
Toast.makeText(getContext(), getResources().getString(R.string.permission_not_granted), Toast.LENGTH_SHORT).show();
}
break;
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
}
});
}

View File

@@ -15,9 +15,14 @@
*/
package com.health.openscale.gui.preferences;
import android.app.Activity;
import static android.content.Context.LOCATION_SERVICE;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.DialogInterface;
@@ -26,12 +31,15 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
@@ -48,6 +56,9 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
@@ -56,9 +67,8 @@ 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.utils.ColorUtil;
import com.health.openscale.gui.utils.PermissionHelper;
import com.welie.blessed.BluetoothCentral;
import com.welie.blessed.BluetoothCentralCallback;
import com.welie.blessed.BluetoothCentralManager;
import com.welie.blessed.BluetoothCentralManagerCallback;
import com.welie.blessed.BluetoothPeripheral;
import java.util.HashMap;
@@ -76,7 +86,7 @@ public class BluetoothSettingsFragment extends Fragment {
private TextView txtSearching;
private ProgressBar progressBar;
private Handler progressHandler;
private BluetoothCentral central;
private BluetoothCentralManager central;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -99,12 +109,110 @@ public class BluetoothSettingsFragment extends Fragment {
@Override
public void onResume() {
if (PermissionHelper.requestBluetoothPermission(this)) {
if (PermissionHelper.requestLocationServicePermission(this)) {
startBluetoothDiscovery();
}
}
super.onResume();
Timber.d("Bluetooth settings Bluetooth permission check");
int targetSdkVersion = getActivity().getApplicationInfo().targetSdkVersion;
final BluetoothManager bluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
// Check if Bluetooth is enabled
if (btAdapter == null || !btAdapter.isEnabled()) {
Timber.d("Bluetooth is not enabled");
Toast.makeText(getContext(), "Bluetooth " + getContext().getResources().getString(R.string.info_is_not_enable), Toast.LENGTH_SHORT).show();
stepNavigationBack();
return;
}
// Check if Bluetooth 4.x is available
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Timber.d("No Bluetooth 4.x available");
Toast.makeText(getContext(), "Bluetooth 4.x " + getContext().getResources().getString(R.string.info_is_not_available), Toast.LENGTH_SHORT).show();
stepNavigationBack();
return;
}
// Check if GPS or Network location service is enabled
LocationManager locationManager = (LocationManager) getActivity().getSystemService(LOCATION_SERVICE);
if (!(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
Timber.d("No GPS or Network location service is enabled, ask user for permission");
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.permission_bluetooth_info_title);
builder.setIcon(R.drawable.ic_preferences_about);
builder.setMessage(R.string.permission_location_service_info);
builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
// Show location settings when the user acknowledges the alert dialog
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
getActivity().startActivity(intent);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
stepNavigationBack();
return;
}
String[] requiredPermissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && targetSdkVersion >= Build.VERSION_CODES.S) {
Timber.d("SDK >= 31 request for Bluetooth Scan and Bluetooth connect permissions");
requiredPermissions = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && targetSdkVersion >= Build.VERSION_CODES.Q) {
Timber.d("SDK >= 29 request for Access fine location permission");
requiredPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
} else {
Timber.d("SDK < 29 request for coarse location permission");
requiredPermissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
}
if (hasPermissions(requiredPermissions)) {
startBluetoothDiscovery();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
Timber.d("No access fine location permission granted");
builder.setMessage(R.string.permission_bluetooth_info)
.setTitle(R.string.permission_bluetooth_info_title)
.setIcon(R.drawable.ic_preferences_about)
.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_SCAN)) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
Timber.d("No access Bluetooth scan permission granted");
builder.setMessage(R.string.permission_bluetooth_info)
.setTitle(R.string.permission_bluetooth_info_title)
.setIcon(R.drawable.ic_preferences_about)
.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
} else {
requestPermissionBluetoothLauncher.launch(requiredPermissions);
}
}
@Override
@@ -123,7 +231,7 @@ public class BluetoothSettingsFragment extends Fragment {
return formatDeviceName(device.getName(), device.getAddress());
}
private final BluetoothCentralCallback bluetoothCentralCallback = new BluetoothCentralCallback() {
private final BluetoothCentralManagerCallback bluetoothCentralCallback = new BluetoothCentralManagerCallback() {
@Override
public void onDiscoveredPeripheral(BluetoothPeripheral peripheral, ScanResult scanResult) {
new Handler().post(new Runnable() {
@@ -139,7 +247,7 @@ public class BluetoothSettingsFragment extends Fragment {
deviceListView.removeAllViews();
foundDevices.clear();
central = new BluetoothCentral(requireContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
central = new BluetoothCentralManager(requireContext(), bluetoothCentralCallback, new Handler(Looper.getMainLooper()));
central.scanForPeripherals();
txtSearching.setVisibility(View.VISIBLE);
@@ -160,21 +268,25 @@ public class BluetoothSettingsFragment extends Fragment {
new Handler().post(new Runnable() {
@Override
public void run() {
BluetoothDeviceView notSupported = new BluetoothDeviceView(requireContext());
notSupported.setDeviceName(requireContext().getString(R.string.label_scale_not_supported));
notSupported.setSummaryText(requireContext().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"));
try {
BluetoothDeviceView notSupported = new BluetoothDeviceView(requireContext());
notSupported.setDeviceName(requireContext().getString(R.string.label_scale_not_supported));
notSupported.setSummaryText(requireContext().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);
}
startActivity(notSupportedIntent);
}
});
deviceListView.addView(notSupported);
} catch(IllegalStateException ex) {
Timber.e(ex.getMessage());
}
}
});
}
}, 20 * 1000);
@@ -193,15 +305,16 @@ public class BluetoothSettingsFragment extends Fragment {
private void onDeviceFound(final ScanResult bleScanResult) {
BluetoothDevice device = bleScanResult.getDevice();
Context context = getContext();
if (device.getName() == null || foundDevices.containsKey(device.getAddress())) {
if (device.getName() == null || foundDevices.containsKey(device.getAddress()) || context == null) {
return;
}
BluetoothDeviceView deviceView = new BluetoothDeviceView(requireContext());
BluetoothDeviceView deviceView = new BluetoothDeviceView(context);
deviceView.setDeviceName(formatDeviceName(bleScanResult.getDevice()));
BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(requireContext(), device.getName());
BluetoothCommunication btDevice = BluetoothFactory.createDeviceDriver(context, device.getName());
if (btDevice != null) {
Timber.d("Found supported device %s (driver: %s)",
formatDeviceName(device), btDevice.driverName());
@@ -213,7 +326,7 @@ public class BluetoothSettingsFragment extends Fragment {
Timber.d("Found unsupported device %s",
formatDeviceName(device));
deviceView.setIcon(R.drawable.ic_bluetooth_device_not_supported);
deviceView.setSummaryText(requireContext().getString(R.string.label_bt_device_no_support));
deviceView.setSummaryText(context.getString(R.string.label_bt_device_no_support));
deviceView.setEnabled(false);
if (OpenScale.DEBUG_MODE) {
@@ -362,37 +475,47 @@ public class BluetoothSettingsFragment extends Fragment {
Timber.d("Saved Bluetooth device " + device.getName() + " with address " + device.getAddress());
stopBluetoothDiscovery();
if (getActivity().findViewById(R.id.nav_host_fragment) != null){
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment).getPreviousBackStackEntry().getSavedStateHandle().set("update", true);
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment).navigateUp();
} else
getActivity().finish();
}
}
private void stepNavigationBack() {
if (getActivity().findViewById(R.id.nav_host_fragment) != null) {
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment).getPreviousBackStackEntry().getSavedStateHandle().set("update", true);
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment).navigateUp();
} else {
getActivity().finish();
}
}
@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)) {
private boolean hasPermissions(String[] permissions) {
if (permissions != null) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
Timber.d("Permission is not granted: " + permission);
return false;
}
Timber.d("Permission already granted: " + permission);
}
return true;
}
return false;
}
private ActivityResultLauncher<String[]> requestPermissionBluetoothLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), isGranted -> {
if (isGranted.containsValue(false)) {
Timber.d("At least one Bluetooth permission was not granted");
Toast.makeText(requireContext(), getString(R.string.label_bluetooth_title) + ": " + getString(R.string.permission_not_granted), Toast.LENGTH_SHORT).show();
stepNavigationBack();
}
else {
startBluetoothDiscovery();
}
}
}
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(requireContext(), R.string.permission_not_granted, Toast.LENGTH_SHORT).show();
}
break;
}
}
}
});
}

View File

@@ -19,6 +19,7 @@ import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
@@ -32,6 +33,9 @@ import com.health.openscale.R;
import com.health.openscale.core.alarm.AlarmHandler;
import com.health.openscale.core.alarm.ReminderBootReceiver;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class ReminderPreferences extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -55,7 +59,14 @@ public class ReminderPreferences extends PreferenceFragmentCompat
prefDays.setSummaryProvider(new Preference.SummaryProvider<MultiSelectListPreference>() {
@Override
public CharSequence provideSummary(MultiSelectListPreference preference) {
return preference.getValues().toString();
final String[] values = getResources().getStringArray(R.array.weekdays_values);
final String[] translated = getResources().getStringArray(R.array.weekdays_entries);
return IntStream.range(0, values.length)
.mapToObj(i -> new Pair<>(values[i], translated[i]))
.filter(p -> preference.getValues().contains(p.first))
.map(p -> p.second)
.collect(Collectors.joining(", "));
}
});

View File

@@ -1,137 +0,0 @@
/* Copyright (C) 2018 olie.xdev <olie.xdev@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.health.openscale.gui.utils;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import com.health.openscale.R;
import static android.content.Context.LOCATION_SERVICE;
public class PermissionHelper {
public final static int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
public final static int PERMISSIONS_REQUEST_ACCESS_READ_STORAGE = 2;
public final static int PERMISSIONS_REQUEST_ACCESS_WRITE_STORAGE = 3;
public final static int ENABLE_BLUETOOTH_REQUEST = 5;
public static boolean requestBluetoothPermission(final Fragment fragment) {
final BluetoothManager bluetoothManager = (BluetoothManager) fragment.getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
if (btAdapter == null || !btAdapter.isEnabled()) {
Toast.makeText(fragment.getContext(), "Bluetooth " + fragment.getContext().getResources().getString(R.string.info_is_not_enable), Toast.LENGTH_SHORT).show();
if (btAdapter != null) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
fragment.getActivity().startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST);
}
return false;
}
// Check if Bluetooth 4.x is available
if (!fragment.getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(fragment.getContext(), "Bluetooth 4.x " + fragment.getContext().getResources().getString(R.string.info_is_not_available), Toast.LENGTH_SHORT).show();
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (fragment.getContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getActivity());
builder.setMessage(R.string.permission_bluetooth_info)
.setTitle(R.string.permission_bluetooth_info_title)
.setIcon(R.drawable.ic_preferences_about)
.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
fragment.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
return false;
}
}
return true;
}
public static boolean requestLocationServicePermission(final Fragment fragment) {
LocationManager locationManager = (LocationManager) fragment.getActivity().getSystemService(LOCATION_SERVICE);
if (!(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getContext());
builder.setTitle(R.string.permission_bluetooth_info_title);
builder.setIcon(R.drawable.ic_preferences_about);
builder.setMessage(R.string.permission_location_service_info);
builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
// Show location settings when the user acknowledges the alert dialog
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
fragment.getActivity().startActivity(intent);
}
});
Dialog alertDialog = builder.create();
alertDialog.setCanceledOnTouchOutside(false);
alertDialog.show();
return false;
}
return true;
}
public static boolean requestReadPermission(final Fragment fragment) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (fragment.getContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
fragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_ACCESS_READ_STORAGE);
} else {
return true;
}
}
return false;
}
public static boolean requestWritePermission(final Fragment fragment) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (fragment.getContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
fragment.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_ACCESS_WRITE_STORAGE);
} else {
return true;
}
}
return false;
}
}

View File

@@ -144,7 +144,7 @@ public class WidgetProvider extends AppWidgetProvider {
// Start main activity when widget is clicked
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);

View File

@@ -23,10 +23,16 @@
android:layout_margin="20px"
android:visibility="gone" />
<LinearLayout
android:id="@+id/deviceListView"
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10px"
android:orientation="vertical" />
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/deviceListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10px"
android:orientation="vertical" />
</ScrollView>
</LinearLayout>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_bluetooth_device_supported"
android:tint="@color/widgetTextColor"/>
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_bluetooth_device_supported" />
<TextView
android:layout_width="wrap_content"
@@ -82,15 +82,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/flat_selector"
android:text="Add Bluetooth scale"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="Add Bluetooth scale"
android:textColor="@android:color/white"
android:textSize="14sp"/>
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_preferences_graph"
android:tint="@color/widgetTextColor" />
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_preferences_graph" />
<TextView
android:id="@+id/slideMainText"
@@ -65,6 +65,6 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_slide_opensource"
android:tint="@color/widgetTextColor" />
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_slide_opensource" />
<TextView
android:id="@+id/slideMainText"
@@ -65,7 +65,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_slide_privacy"
android:tint="@color/widgetTextColor" />
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_slide_privacy" />
<TextView
android:id="@+id/slideMainText"
@@ -65,6 +65,6 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_slide_support"
android:tint="@color/widgetTextColor" />
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_slide_support" />
<TextView
android:id="@+id/slideMainText"
@@ -65,6 +65,6 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -48,8 +48,8 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginBottom="40dp"
app:srcCompat="@drawable/ic_slide_group"
android:tint="@color/widgetTextColor" />
app:tint="?android:attr/textColorSecondary"
app:srcCompat="@drawable/ic_slide_group" />
<TextView
android:layout_width="wrap_content"
@@ -72,15 +72,15 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/flat_selector"
android:text="@string/label_add_user"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="@string/label_add_user"
android:textColor="@android:color/white"
android:textSize="14sp"/>
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -23,8 +23,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:text="@string/label_slide_welcome_top_text"
android:gravity="center"
android:text="@string/label_slide_welcome_top_text"
android:textSize="26sp"
android:textStyle="bold" />
@@ -41,6 +41,6 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="250px"/>
android:layout_marginBottom="250px" />
</LinearLayout>
</ScrollView>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources><string name="title_overview">نظرة عامة</string>
<resources>
<string name="title_overview">نظرة عامة</string>
<string name="title_graph">جدولة</string>
<string name="title_table">لائحة</string>
<string name="title_statistics">إحصائيات</string>
@@ -19,31 +20,30 @@
<string name="label_add_user">إضافة مستعمل</string>
<string name="label_add_measurement">اضافة قياس</string>
<string name="label_share">مشاركة</string>
<string name="label_share_subject">"تصدرير بيانات CSV "</string>
<string name="label_share_subject">تصدرير بيانات CSV (%s)</string>
<string name="label_weight">وزن</string>
<string name="label_bmi">"مؤشر كتلة الجسم (BMI) "</string>
<string name="label_bmi">مؤشر كتلة الجسم (BMI)</string>
<string name="label_bmr">معدل الأيض الأساسي (BMR)</string>
<string name="label_tdee">مجموع نفقات الطاقة اليومية (TDEE)</string>
<string name="label_fat">دهون الجسم</string>
<string name="label_water">ماء الجسم</string>
<string name="label_water">إجمالي مياه الجسم</string>
<string name="label_muscle">العضلات</string>
<string name="label_lbm">كتلة بدون دهنيات</string>
<string name="label_waist">محيط الخصر
\n</string>
<string name="label_waist">محيط الخصر</string>
<string name="label_hip">محيط الورك</string>
<string name="label_comment">علق</string>
<string name="label_whtr">نسبة الخصر إلى الطول</string>
<string name="label_whr">نسبة الخصر إلى الورك</string>
<string name="label_bone">كتلة عضمية</string>
<string name="label_smartUserAssign">"التعيين الذكي للمستخدم "</string>
<string name="label_smartUserAssign">التعيين الذكي للمستخدم</string>
<plurals name="label_days">
<item quantity="zero">صفر</item>
<item quantity="one">واحد</item>
<item quantity="two">اثنان</item>
<item quantity="few">بعض</item>
<item quantity="many">كثيرة</item>
<item quantity="other">اخرى</item>
</plurals>
<item quantity="zero">صفر %d</item>
<item quantity="one">واحد %d</item>
<item quantity="two">اثنان %d</item>
<item quantity="few">بعض %d</item>
<item quantity="many">كثيرة %d</item>
<item quantity="other">اخرى %d</item>
</plurals>
<string name="label_last_week">الأسبوع الماضي</string>
<string name="label_last_month">الشهر الماضي</string>
<string name="label_weight_difference">فرق الوزن</string>
@@ -68,12 +68,12 @@
<string name="label_delete_all">حذف الكل</string>
<string name="error_value_required">القيمة المطلوبة</string>
<string name="error_value_range">قيمة خارج النطاق</string>
<string name="error_exporting">خطأ في التصدير</string>
<string name="error_importing">خطأ في الاستيراد</string>
<string name="error_user_name_required">خطأ: الاسم مطلوب</string>
<string name="error_height_required">خطأ: الطول مطلوب</string>
<string name="error_initial_weight_required">خطأ: الوزن الأولي مطلوب</string>
<string name="error_goal_weight_required">خطأ: مطلوب الوزن الهدف</string>
<string name="error_exporting">تعذر التصدير</string>
<string name="error_importing">تعذر الاستيراد</string>
<string name="error_user_name_required">الاسم مطلوب</string>
<string name="error_height_required">الطول مطلوب</string>
<string name="error_initial_weight_required">الوزن الأولي مطلوب</string>
<string name="error_goal_weight_required">الوزن المستهدف مطلوب</string>
<string name="info_data_deleted">إدخال حذف</string>
<string name="info_data_all_deleted">كل الادخالات حذفت</string>
<string name="info_data_exported">تم التصدير إلى</string>
@@ -86,14 +86,14 @@
<string name="info_is_not_enable">غير مشغل</string>
<string name="info_is_not_available">غير موجود</string>
<string name="info_bluetooth_try_connection">الاتصال ب:</string>
<string name="info_bluetooth_connection_lost">اتصال بلوتوث فقد</string>
<string name="info_bluetooth_connection_lost">انقطع اتصال البلوتوث</string>
<string name="info_bluetooth_no_device">لم يتم العثور على جهاز بلوتوث</string>
<string name="info_bluetooth_no_device_retrying">لا يمكن إنشاء اتصال ، جارٍ إعادة المحاولة …</string>
<string name="info_bluetooth_no_device_set">لم يتم تحديد أي جهاز بلوتوث</string>
<string name="info_bluetooth_connection_successful">تم الاتصال</string>
<string name="info_bluetooth_no_device_retrying">تعذر إنشاء اتصال ، إعادة المحاولة …</string>
<string name="info_bluetooth_no_device_set">اختار جهاز بلوتوث</string>
<string name="info_bluetooth_connection_successful">متصل</string>
<string name="info_bluetooth_init">تهيئة جهاز بلوتوث</string>
<string name="info_bluetooth_connection_error">خطأ غير متوقع في البلوتوث</string>
<string name="info_bluetooth_connection_disconnected">"اغلق اتصال بلوتوث "</string>
<string name="info_bluetooth_connection_disconnected">اغلق اتصال بلوتوث</string>
<string name="info_new_data_duplicated">القياس مع نفس التاريخ والوقت موجود بالفعل</string>
<string name="info_enter_user_name">إسمك</string>
<string name="info_no_selected_user">لا يوجد مستخدم. الرجاء إنشاء واحد في الإعدادات.</string>
@@ -105,10 +105,10 @@
<string name="label_bluetooth_enable">الاتصال بالميزان عند بدء التشغيل</string>
<string name="label_mergeWithLastMeasurement">دمج مع القياس الاخير</string>
<string name="label_bluetooth_searching">البحث عن ميزان البلوتوث</string>
<string name="label_bluetooth_searching_finished">"انتهى البحث "</string>
<string name="label_bluetooth_searching_finished">انتهى البحث</string>
<string name="label_enable_labels">تسمية البيانات</string>
<string name="label_enable_points">نقطة البيانات</string>
<string name="label_delete_confirmation">"تأكيد الحذف "</string>
<string name="label_delete_confirmation">تأكيد الحذف</string>
<string name="label_category_measurement_database">قاعدة بيانات القياسات</string>
<string name="label_website">موقع</string>
<string name="label_license">رخصة</string>
@@ -172,12 +172,13 @@
<string name="label_export_overwrite">هل تريد استبدال التصدير السابق\"%s\"؟</string>
<string name="label_measurement_in_percent">القياس ب ٪</string>
<string name="label_estimate_measurement">تقدير القياس</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] ل %4 $s اضيف</string>
<string name="info_new_data_added">%4$s أضيفت إلى [%3$s] %2$s%1$.2f</string>
<string name="info_measuring">%.2f قياس الوزن : هو</string>
<string name="trisa_scale_not_paired">لم يتم إقران مع هذا الميزان!
\n
\nاضغط على الزر الموجود أسفل الميزان لتحويله إلى وضع الإقران ، ثم أعد توصيله لاسترداد كلمة مرور الجهاز.</string>
<string name="trisa_scale_pairing_succeeded">نجح الاقتران!
\n
\nأعد الاتصال لاسترداد بيانات القياس.</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">حدث خطأ غير متوقع.
\n
@@ -227,4 +228,76 @@
<string name="activity_level_moderate">معتدل</string>
<string name="activity_level_heavy">كثيف</string>
<string name="activity_level_extreme">شديد</string>
<string name="label_prediction">التنبؤ</string>
<string name="label_age">العمر</string>
<string name="info_assisted_weighing_old_reference_measurement">تحذير: آخر قياس مرجعي مضى عليه أكثر من يوم</string>
<string name="info_assisted_weighing_choose_reference_user">اختر المستخدم المرجعي الذي يتم طرح الوزن</string>
<string name="info_assisted_weighing_no_reference_user">لا يوجد مستخدم مرجعي متاح ، يرجى إنشاء واحد</string>
<string name="info_scale_low_battery">مستوى البطارية منخفض(%d%%) ، يرجى إعادة شحن أو استبدال بطاريات الميزان</string>
<string name="label_enable_yaxis">المحور Y</string>
<string name="info_bluetooth_connection_error_scale_offline">تعذر الاتصال بالميزان، يرجى التأكد من تشغيله.</string>
<string name="label_enable_legend">أسطورة الرسم البياني</string>
<string name="label_trend_line">خط الأساس</string>
<string name="label_trend">الاتجاه</string>
<string name="label_assisted_weighing">الوزن بمساعدة</string>
<string name="label_year_view">عرض السنة</string>
<string name="label_is_on_right_axis">يقع على المحور الأيمن</string>
<string name="permission_location_service_info">امنح الوصول إلى الموقع الجغرافي في إعدادات أندرويد للبحث عن أجهزة بلوتوث. يمكنك الإلغائ بعد ذلك.</string>
<string name="amputation_level_none">لا بتر</string>
<string name="amputation_level_foot">قدم</string>
<string name="label_upgrade_to_openScale_pro">يرجى الترقية إلى أوبن سكيل برو لدعم البلوتوث</string>
<string name="label_slide_privacy_main_text">لا يرسل openScale أي بيانات إلى سحابة وعدم وجود إذن للوصول إلى الإنترنت هو ضمان قوي لذلك.
\n
\nإذا كنت تريد حقا مزامنة وزنك مع GoogleFit أو مع MQTT ، فيمكنك تثبيت <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync"> openScale sync </a>.</string>
<string name="label_slide_bluetooth_main_text">يحتوي اوبن سكيل على دعم مدمج لعدد من موازين البلوتوث من مختلف الشركات المصنعة.
\n
\nنحن نعمل باستمرار على تحسين وتوسيع مجموعة الموازين المدعومة. يمكنك العثور على القائمة الكاملة ومستوى الدعم لكل مقياس على <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale"> GitHub</a>.</string>
<string name="label_slide_opensource_main_text">اوبن سكيل مرخص بموجب <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE"> GPLv3 </a>.
\n
\nجميع حسابات الجسم شفافة ولا يتم إجراء أي وظيفة نقل بيانات مخفية أو تحديد هوية المستخدم.
\n
\nيمكنك العثور على شفرة المصدر الكاملة على <a href="https://github.com/oliexdev/openScale"> GitHub</a>. أقنع نفسك ب اوبن سكيل من خلال مراجعته.</string>
<string name="label_slide_support_main_text">أوبن سكيل يحتاج إليك. أنشئ مشكلة جديدة <a href="https://github.com/oliexdev/openScale/issues"> </a> إذا عثرت على خطأ أو كانت لديك فكرة أو سؤال أو تريد المساعدة في دعم مقياس البلوتوث الخاص بك.
\n
\nحفز منشئ هذا المشروع على مزيد من التطوير والصيانة المستمرة من خلال منح أوبن سكيل تقييما إيجابيا على <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro"> GooglePlay </a> أو نجمة على <a href="https://github.com/oliexdev/openScale"> GitHub </a>.
\n
\nدعمكم وردود الفعل الإيجابية هو موضع تقدير كبير. شكرًا.</string>
<string name="info_assisted_weighing_no_reference_measurements">ليس لدى المستخدم المرجعي قياسات</string>
<string name="label_amputation_left">البتر الأيسر</string>
<string name="label_amputation_right">البتر اليمين</string>
<string name="label_amputation">بتر</string>
<string name="label_empty">فارغ</string>
<string name="action_donation">تبرّع</string>
<string name="info_select_scale_user">حدد مستخدم الميزان</string>
<string name="info_enter_consent_code_for_scale_user">أدخل رمز PIN/الموافقة لمستخدم المقياس %s</string>
<string name="label_day_view">عرض اليوم</string>
<string name="label_measurement_bar">شريط القياس</string>
<string name="amputation_level_hand">يد</string>
<string name="amputation_level_forearm_hand">اللؤلؤ والساق</string>
<string name="amputation_level_arm">ذراع</string>
<string name="amputation_level_lower_leg_foot">الساق السفلية والقدم</string>
<string name="amputation_level_leg">ساق</string>
<string name="label_slide_welcome_top_text">مرحبا بكم في
\nأوبن سكيل</string>
<string name="label_slide_welcome_main_text">برنامج مفتوح المصدر لتعقب وزن الجسم ومقاييس الجسم ، مع دعم ميزان البلوتوث.</string>
<string name="label_slide_privacy_top_text">حماية بياناتك الصحية الشخصية.</string>
<string name="label_slide_user_top_text">يتم دعم العديد من المستخدمين.</string>
<string name="label_slide_user_main_text">اوبن سكيل لا يُطلب منك إنشاء حساب على الإنترنت
\n
\nوتحسب مقاييس الجسم من القياسات.</string>
<string name="label_slide_opensource_top_text">إنه برنامج مفتوح المصدر بدرجة عالية من الشفافية.</string>
<string name="label_slide_bluetooth_top_text">يتم دعم العديد من موازين البلوتوث من مختلف الشركات المصنعة.</string>
<string name="label_slide_metrics_top_text">تتبع وتحلل مقاييس جسمك.</string>
<string name="label_slide_support_top_text">ساعد في تحسين اوبن سكيل.</string>
<string name="app_intro_skip_button">تخطي</string>
<string name="app_intro_next_button">التالي</string>
<string name="app_intro_back_button">العودة</string>
<string name="app_intro_done_button">منجز</string>
<string name="info_create_new_user_on_scale">إنشاء مستخدم جديد على الميزان.</string>
<string name="label_slide_metrics_main_text">يدعم اوبن سكيل أكثر من 22 مقياسا للجسم.
\n
\nقم بتكوينه لإظهار تلك التي تهتم بها.
\n
\n
\nإذا كانت الدهون في الجسم والماء وكتلة الجسم الخالية من الدهون مفقودة ، فيمكن تقديرها بناء على <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">المنشورات العلمية </a>.</string>
</resources>

View File

@@ -38,7 +38,7 @@
<string name="label_yes">হ্যাঁ</string>
<string name="label_cancel">বাতিল</string>
<string name="close_drawer">বন্ধ</string>
<string name="open_drawer">খোলা</string>
<string name="open_drawer">ুল</string>
<string name="action_donation">দান</string>
<string name="action_bluetooth_status">ব্লুটুথ অবস্থা</string>
<string name="action_settings">নিয়ম বিন্যাস</string>

View File

@@ -128,7 +128,7 @@
<string name="title_general">General</string>
<string name="label_add_measurement">Afegeix una mesura</string>
<string name="label_share">Comparteix</string>
<string name="label_share_subject">Exporta les dades com a CSV (%s)</string>
<string name="label_share_subject">Exportació de dades de l\'openScale (%s)</string>
<string name="info_bluetooth_no_device_retrying">No s\'ha pogut establir una connexió, s\'està tornant a provar…</string>
<string name="info_bluetooth_no_device_set">Seleccioneu un dispositiu Bluetooth</string>
<string name="info_bluetooth_connection_disconnected">S\'ha tancat la connexió Bluetooth</string>
@@ -283,10 +283,16 @@
<string name="amputation_level_none">Sense amputació</string>
<string name="info_assisted_weighing_choose_reference_user">Trieu l\'usuari de referència del qual es restarà el pes</string>
<string name="info_assisted_weighing_no_reference_user">No hi ha cap usuari de referència disponible, creeu-ne un</string>
<string name="info_assisted_weighing_old_reference_measurement">Atenció, la darrera mesura de referència és de fa més d\'un dia</string>
<string name="info_assisted_weighing_old_reference_measurement">Atenció: la darrera mesura de referència és de fa més d\'un dia</string>
<string name="info_assisted_weighing_no_reference_measurements">L\'usuari de referència no té cap mesura</string>
<string name="label_amputation_right">Amputació dreta</string>
<string name="label_amputation_left">Amputació esquerra</string>
<string name="label_amputation">Amputació</string>
<string name="label_assisted_weighing">Pesatge assistit</string>
<string name="label_trend">Tendència</string>
<string name="label_prediction">Predicció</string>
<string name="label_trend_line">Línia de tendència</string>
<string name="info_select_scale_user">Seleccioneu l\'usuari de la bàscula</string>
<string name="info_enter_consent_code_for_scale_user">Introduïu el PIN/codi de consentiment per a l\'usuari de la bàscula %s</string>
<string name="info_create_new_user_on_scale">Creeu un nou usuari a la bàscula.</string>
</resources>

View File

@@ -156,7 +156,7 @@
<string name="edit">Redigér</string>
<string name="label_month_view">Månedsvis</string>
<string name="permission_not_granted">Tilladelse er ikke givet</string>
<string name="permission_bluetooth_info">Tilladelse til lokalbestemmelse er påkrævet for at søge efter Bluetooth-enheder.</string>
<string name="permission_bluetooth_info">Tilladelse til lokal bestemmelse er påkrævet for at søge efter Bluetooth-enheder.</string>
<string name="permission_bluetooth_info_title">Information</string>
<string name="label_share_subject">CSV dataeksport (%s)</string>
<string name="label_next">Næste</string>

View File

@@ -2,8 +2,8 @@
<resources>
<string name="action_settings">Einstellungen</string>
<string name="error_height_required">Körpergröße ist erforderlich</string>
<string name="error_exporting">Export fehlgeschlagen</string>
<string name="error_importing">Import fehlgeschlagen</string>
<string name="error_exporting">Fehler beim Exportieren</string>
<string name="error_importing">Fehler beim Importieren</string>
<string name="error_user_name_required">Benutzername erforderlich</string>
<string name="info_data_all_deleted">Alle Einträge wurden gelöscht</string>
<string name="info_data_deleted">Eintrag wurde gelöscht</string>
@@ -51,7 +51,7 @@
<string name="label_title_last_measurement">Letzte Messung</string>
<string name="label_title_user">Benutzer</string>
<string name="label_user_name">Name</string>
<string name="label_water">Körperwasseranteil</string>
<string name="label_water">Wassergehalt</string>
<string name="label_weight">Gewicht</string>
<string name="label_weight_difference">Gewichtsunterschied</string>
<string name="label_female">Weiblich</string>
@@ -74,7 +74,7 @@
<string name="label_whr">Taille-Hüft-Verhältnis</string>
<string name="action_bluetooth_status">Bluetooth-Status</string>
<string name="info_bluetooth_connection_error">Unerwarteter Bluetooth-Fehler</string>
<string name="info_bluetooth_connection_lost">Bluetooth Verbindung unterbrochen</string>
<string name="info_bluetooth_connection_lost">Bluetooth-Verbindung verloren</string>
<string name="info_bluetooth_connection_successful">Verbunden</string>
<string name="info_bluetooth_no_device">Kein Bluetooth Gerät gefunden</string>
<string name="info_bluetooth_try_connection">Verbinde mit:</string>
@@ -114,7 +114,7 @@
<string name="label_bone">Knochenmasse</string>
<string name="label_bmr">Grundumsatz (BMR)</string>
<string name="info_measuring">Messe Gewicht: %.2f</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] zu %4$s hinzugefügt</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] hinzugefügt zu %4$s</string>
<string name="error_max_scale_users">Maximale Anzahl gleichzeitiger Benutzer erreicht</string>
<string name="info_step_on_scale_for_reference">Bitte steigen Sie barfuß auf die Waage zur Referenzmessung</string>
<string name="label_automatic">auto</string>
@@ -123,10 +123,10 @@
<string name="label_license">Lizenz</string>
<string name="label_maintainer">Hauptentwickler</string>
<string name="label_website">Webseite</string>
<string name="title_about">Info</string>
<string name="title_about">Über</string>
<string name="open_drawer">öffnen</string>
<string name="close_drawer">schließen</string>
<string name="info_bluetooth_no_device_set">Bitte ein Bluetooth-Gerät auswählen</string>
<string name="info_bluetooth_no_device_set">Wählen Sie ein Bluetooth-Gerät aus</string>
<string name="info_new_data_duplicated">Messung mit gleichen Datum und Uhrzeit existiert bereits</string>
<string name="label_feedback_message_enjoying">Gefällt Dir openScale?</string>
<string name="label_feedback_message_rate_app">Wie wäre es dann mit einer Bewertung bei GooglePlay oder bei GitHub?</string>
@@ -158,11 +158,11 @@
<string name="label_month_view">Monatsansicht</string>
<string name="label_weeks_view">Wochenansicht</string>
<string name="permission_not_granted">Zugriff verweigert</string>
<string name="permission_bluetooth_info">Erlaubnis erforderlich um auf den Standort zuzugreifen, um nach Bluetooth-Geräten zu suchen. Sobald das Gerät gefunden wurde kann diese widerrufen werden.</string>
<string name="permission_bluetooth_info">Erlaubnis für Standort Zugriff benötigt, um nach Bluetooth-Geräten zu suchen. Sobald das Gerät gefunden wurde kann diese widerrufen werden.</string>
<string name="permission_bluetooth_info_title">Information</string>
<string name="label_next">Weiter</string>
<string name="label_set_default_order">Default Reihenfolge wiederherstellen</string>
<string name="label_share_subject">CSV Datenexport (%s)</string>
<string name="label_share_subject">openScale datenexport (%s)</string>
<string name="label_mergeWithLastMeasurement">Zusammenführen mit der letzten Messung</string>
<string name="label_export_overwrite">Vorherigen Export überschreiben %s?</string>
<string name="label_measurement_in_percent">Messwert in %</string>
@@ -179,13 +179,13 @@
<string name="label_estimate_measurement_summary">Basierend auf Gewicht, Größe, Alter, Geschlecht, etc.</string>
<string name="label_scale_not_supported">Wird deine Waage nicht unterstützt?</string>
<string name="label_click_to_help_add_support">Klicke hier, wenn du helfen möchtest</string>
<string name="label_auto_backup">Automatiches Backup</string>
<string name="label_auto_backup">Automatisches Backup</string>
<string name="label_auto_backup_schedule">Backup Zeitplan</string>
<string name="label_overwrite_backup">Überschreibe Backup</string>
<string name="label_daily">täglich</string>
<string name="label_weekly">wöchentlich</string>
<string name="label_monthly">monatlich</string>
<string name="label_long_press_drag_reorder">Zum Neuordnen der Messwerte lange drücken und halten</string>
<string name="label_long_press_drag_reorder">Langes Drücken und Halten zum Neuordnen</string>
<string name="label_development">Entwicklung</string>
<string name="label_select_measurement">Messwert auswählen</string>
<string name="label_select_user">Benutzer auswählen</string>
@@ -219,8 +219,8 @@
<string name="trisa_scale_pairing_succeeded">Die Kopplung ist erfolgreich!
\n
\nWieder verbinden, um Messdaten abzurufen.</string>
<string name="info_bluetooth_no_device_retrying">Kann keine Verbindung herstellen, versuche erneut…</string>
<string name="label_tdee">Täglicher Energiebedarf (TDEE)</string>
<string name="info_bluetooth_no_device_retrying">Kann keine Verbindung herstellen; versuche erneut </string>
<string name="label_tdee">Gesamttagesenergieumsatz (TDEE)</string>
<string name="label_calories">Kalorien</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth Verbindung getrennt</string>
<string name="info_step_on_scale">Bitte Barfuß auf die Wage stellen</string>
@@ -234,19 +234,19 @@
<string name="label_upgrade_to_openScale_pro">Bitte upgraden Sie auf openScale pro für Bluetooth-Unterstützung</string>
<string name="action_donation">Spende</string>
<string name="info_scale_low_battery">Niedriger Batteriestand (%d%%), bitte aufladen oder Batterien der Waage austauschen</string>
<string name="info_bluetooth_connection_error_scale_offline">Keine Verbindung zur Waage, bitte überprüfen, ob sie eingeschaltet ist.</string>
<string name="info_bluetooth_connection_error_scale_offline">Keine Verbindung zur Waage, bitte prüfen Sie, ob Ihre Waage eingeschaltet ist.</string>
<string name="label_empty">leer</string>
<string name="label_age">Alter</string>
<string name="label_slide_welcome_top_text">Willkommen zu
\nopenScale</string>
<string name="label_slide_welcome_main_text">Open-Source Tracker für Gewichts- und Körpermetriken mit Bluetooth-Waagen Anbindung.</string>
<string name="label_slide_welcome_main_text">Open-Source Tracker für Gewichts- und Körpermetriken, mit Unterstützung für Bluetooth-Waagen.</string>
<string name="label_slide_privacy_top_text">Schützt Ihre persönlichen Gesundheitsdaten.</string>
<string name="label_slide_privacy_main_text">openScale sendet keine Daten an eine Cloud und die fehlende Zugriffsberechtigung auf das Internet ist eine starke Garantie dafür.
\n
\nWenn Sie Ihr Gewicht wirklich mit GoogleFit oder mit MQTT synchronisieren wollen, können Sie <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a> installieren.</string>
<string name="label_slide_user_top_text">Es werden mehrere Benutzer unterstützt.</string>
<string name="label_slide_user_main_text">openScale verlangt nicht, dass Sie ein Online-Konto erstellen.
\n
<string name="label_slide_user_main_text">openScale verlangt nicht, dass Sie ein Online-Konto erstellen.
\n
\nKörpermetriken werden anhand der Messwerte berechnet.</string>
<string name="label_slide_opensource_top_text">Es handelt sich um Open-Source Software mit einem hohen Maß an Transparenz.</string>
<string name="label_slide_opensource_main_text">openScale ist lizenziert unter der <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
@@ -259,17 +259,17 @@
\n
\nWir verbessern und erweitern ständig den Umfang der unterstützten Waagen. Die vollständige Liste und den Grad der Unterstützung für jede Waage finden Sie auf <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_slide_metrics_top_text">Verfolgen und analysieren Sie Ihre Körpermetriken.</string>
<string name="label_slide_metrics_main_text">openScale unterstützt über 22 Körpermetriken.
\n
\nSie legen fest, welche davon beobachtet werden sollen.
\n
\nWenn Ihre Waage Körperfett, Körperwasser und Muskelmasse nicht unterstützt, können Sie diese Messungen auf der Grundlage von <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">wissenschaftlichen Publikationen</a> schätzen lassen.</string>
<string name="label_slide_support_top_text">Helfen Sie, openScale zu verbessern.</string>
<string name="label_slide_support_main_text">openScale braucht dich! Wenn du einen Fehler gefunden hast, eine Idee oder eine Frage hast oder helfen möchtest, deine Bluetooth Waage zu unterstützen, erstelle bitte ein <a href="https://github.com/oliexdev/openScale/issues">neues Ticket</a> auf GitHub.
\n
\nWenn Sie den Entwickler dieses Projekts in der Weiterentwicklung und der kontinuierlichen Pflege motivieren möchten, geben Sie openScale eine positive Bewertung auf <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> oder einen Stern auf <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nIhre Unterstützung und Ihr positives Feedback sind herzlichst willkommen. Vielen dank.</string>
<string name="label_slide_metrics_main_text">openScale unterstützt über 22 Körpermetriken.
\n
\nEs kann individuell konfiguriert werden, um nur die Körpermetriken anzuzeigen, die Ihnen wichtig sind.
\n
\nWenn Ihre Waage Körperfett, Körperwasser und Muskelmasse nicht unterstützt, können Sie diese Werte auf der Grundlage von <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">wissenschaftlichen Publikationen</a> schätzen lassen.</string>
<string name="label_slide_support_top_text">Helfen Sie uns, openScale zu verbessern.</string>
<string name="label_slide_support_main_text">openScale braucht dich! Erstelle ein <a href="https://github.com/oliexdev/openScale/issues">neues Ticket</a> auf GitHub, wenn du einen Fehler gefunden hast, eine Idee oder eine Frage hast oder helfen möchtest, deine Bluetooth Waage zu unterstützen.
\n
\nWenn Sie den Entwickler dieses Projekts in der Weiterentwicklung und der kontinuierlichen Pflege motivieren möchten, geben Sie openScale gerne eine positive Bewertung auf <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> oder einen Stern auf <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nIhre Unterstützung und Ihre positive Rückmeldung sind herzlichst willkommen. Vielen Dank.</string>
<string name="app_intro_skip_button">Überspringen</string>
<string name="app_intro_next_button">Weiter</string>
<string name="app_intro_back_button">Zurück</string>
@@ -283,10 +283,16 @@
<string name="amputation_level_none">Keine Amputation</string>
<string name="info_assisted_weighing_choose_reference_user">Wählen Sie den Referenz Benutzer, von dem das Gewicht subtrahiert wird</string>
<string name="info_assisted_weighing_no_reference_user">Kein Referenz Benutzer verfügbar, bitte erstellen sie einen</string>
<string name="info_assisted_weighing_old_reference_measurement">Warnung letzte Messung ist älter als ein Tag</string>
<string name="info_assisted_weighing_old_reference_measurement">Warnung: letzte Messung ist älter als ein Tag</string>
<string name="info_assisted_weighing_no_reference_measurements">Referenz Benutzer hat keine Messwerte</string>
<string name="label_amputation_right">Amputation rechts</string>
<string name="label_amputation_left">Amputation links</string>
<string name="label_amputation">Amputation</string>
<string name="label_assisted_weighing">Assistiertes Wiegen</string>
<string name="label_trend">Tendenz</string>
<string name="label_prediction">Prognose</string>
<string name="label_trend_line">Trendlinie</string>
<string name="info_select_scale_user">Benutzer auswählen</string>
<string name="info_enter_consent_code_for_scale_user">PIN/Zustimmungscode eingeben für Benutzer %s</string>
<string name="info_create_new_user_on_scale">Neuen Benutzer auf der Waage erstellen.</string>
</resources>

View File

@@ -12,19 +12,19 @@
<string name="action_bluetooth_status">Κατάσταση Bluetooth</string>
<string name="open_drawer">άνοιξε</string>
<string name="close_drawer">κλείσε</string>
<string name="label_cancel">ακύρωσε</string>
<string name="label_ok">εντάξει</string>
<string name="label_cancel">Άκυρο</string>
<string name="label_ok">Εντάξει</string>
<string name="label_yes">Ναι</string>
<string name="label_no">Όχι</string>
<string name="label_delete">Διάγραψε</string>
<string name="label_add_user">Πρόσθεσε χρήστη</string>
<string name="label_share">Μοιράσου</string>
<string name="label_share_subject">εξαγωγή δεδομένων CSV (%s)</string>
<string name="label_delete">Διαγραφή</string>
<string name="label_add_user">Προσθήκη χρήστη</string>
<string name="label_share">Κοινοποίηση</string>
<string name="label_share_subject">Εξαγωγή δεδομένων openScale (%s)</string>
<string name="label_weight">Βάρος</string>
<string name="label_bmi">Δείκτης μάζας σώματος (ΔΜΣ)</string>
<string name="label_bmr">Βασικός μεταβολικός ρυθμός (ΒΜΡ)</string>
<string name="label_fat">Λίπος σώματος</string>
<string name="label_water">Νερό σώματος</string>
<string name="label_water">Συνολικό νερό σώματος</string>
<string name="label_muscle">Μυς</string>
<string name="label_lbm">Άπαχη μάζα σώματος</string>
<string name="label_waist">Περιφέρεια μέσης</string>
@@ -33,7 +33,7 @@
<string name="label_whtr">Αναλογία μέσης-ύψους</string>
<string name="label_whr">Αναλογία μέσης-γοφών</string>
<string name="label_bone">Μάζα οστών</string>
<string name="label_smartUserAssign">Ανάθεση εργασίας Έξυπνου Χρήστη</string>
<string name="label_smartUserAssign">Ανάθεση Έξυπνου Χρήστη</string>
<plurals name="label_days">
<item quantity="one">ημέρα %d</item>
<item quantity="other">ημέρες %d</item>
@@ -62,12 +62,12 @@
<string name="label_delete_all">Διέγραψε όλα</string>
<string name="error_value_required">Τιμή απαραίτητη</string>
<string name="error_value_range">Τιμή εκτός εύρους</string>
<string name="error_exporting">Σφάλμα κατά την εξαγωγή</string>
<string name="error_importing">Σφάλμα κατά την εισαγωγή</string>
<string name="error_user_name_required">Σφάλμα: Όνομα απαραίτητο</string>
<string name="error_height_required">Σφάλμα: Ύψος απαραίτητο</string>
<string name="error_initial_weight_required">Σφάλμα: Αρχικό βάρος απαραίτητο</string>
<string name="error_goal_weight_required">Σφάλμα: Στόχος βάρους απαραίτητο</string>
<string name="error_exporting">Δεν μπορεί να γίνει εξαγωγή</string>
<string name="error_importing">Δεν μπορεί να γίνει εισαγωγή</string>
<string name="error_user_name_required">Το όνομα είναι απαραίτητο</string>
<string name="error_height_required">Το ύψος είναι απαραίτητο</string>
<string name="error_initial_weight_required">Το αρχικό βάρος είναι απαραίτητο</string>
<string name="error_goal_weight_required">Ο στόχος βάρους είναι απαραίτητος</string>
<string name="info_data_deleted">Καταχώρηση διεγράφη</string>
<string name="info_data_all_deleted">Όλες οι καταχωρήσεις διεγράφησαν</string>
<string name="info_data_exported">Εξήχθη σε</string>
@@ -80,10 +80,10 @@
<string name="info_is_not_enable">απενεργοποιημένο</string>
<string name="info_is_not_available">μη διαθέσιμο</string>
<string name="info_bluetooth_try_connection">Σύνδεση σε:</string>
<string name="info_bluetooth_connection_lost">Χάθηκε σύνδεση με Bluetooth</string>
<string name="info_bluetooth_connection_lost">Χάθηκε η σύνδεση με Bluetooth</string>
<string name="info_bluetooth_no_device">Δεν βρέθηκε συσκευή Bluetooth</string>
<string name="info_bluetooth_no_device_set">Δεν επιλέχθηκε συσκευή Bluetooth</string>
<string name="info_bluetooth_connection_successful">Σύνδεση εδραιώθηκε</string>
<string name="info_bluetooth_no_device_set">Επιλογή συσκευής Bluetooth</string>
<string name="info_bluetooth_connection_successful">Συνδέθηκε</string>
<string name="info_bluetooth_init">Αρχικοποίησε συσκευή Bluetooth</string>
<string name="info_bluetooth_connection_error">Απρόσμενο σφάλμα Bluetooth</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] σε %4$s προσετέθη</string>
@@ -94,19 +94,19 @@
<string name="question_really_delete">Διαγραφή καταχώρησης;</string>
<string name="question_really_delete_all">Διαγραφή όλων των καταχωρήσεων για όλους τους χρήστες;</string>
<string name="question_really_delete_user">Διαγραφή χρήστη;</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Συνέδεσε στη ζυγαριά με το ξεκίνημα</string>
<string name="label_mergeWithLastMeasurement">Συγχώνευσε με τελευταία μέτρηση</string>
<string name="label_bluetooth_searching">Αναζητώντας για την ζυγαριά σας με Bluetooth ικανότητα</string>
<string name="label_bluetooth_searching_finished">Αναζήτηση ολοκληρώθηκε</string>
<string name="label_bluetooth_title">Ζυγαριά Bluetooth</string>
<string name="label_bluetooth_enable">Σύνδεση ζυγαριάς στην έναρξη</string>
<string name="label_mergeWithLastMeasurement">Συγχώνευση με τελευτ. μέτρηση</string>
<string name="label_bluetooth_searching">Αναζήτηση της Bluetooth ζυγαριάς σας</string>
<string name="label_bluetooth_searching_finished">Η αναζήτηση ολοκληρώθηκε</string>
<string name="label_enable_labels">Ετικέτα δεδομένων</string>
<string name="label_enable_points">Σημείο δεδομένων</string>
<string name="label_delete_confirmation">Επιβεβαίωση διαγραφής</string>
<string name="label_category_measurement_database">Βάση δεδομένων μετρήσεων</string>
<string name="label_maintainer">Συντηρητής</string>
<string name="label_website">Ιστοσελίδα</string>
<string name="label_website">Ιστότοπος</string>
<string name="label_license">Άδεια</string>
<string name="label_automatic">αυτόματο</string>
<string name="label_automatic">αυτόματη</string>
<string name="label_theme">Θέμα</string>
<string name="label_reminder">Υπενθύμιση</string>
<string name="label_reminder_weekdays">Ημέρες</string>
@@ -125,8 +125,8 @@
<string name="label_exportBackup">Εξήγαγε αντίγραφο ασφαλείας</string>
<string name="label_importBackup">Εισήγαγε αντίγραφο ασφαλείας</string>
<string name="label_backup">Αντίγραφο ασφαλείας</string>
<string name="label_export_dir">Εξήγαγε κατάλογο</string>
<string name="label_not_found">δεν βρέθηκε</string>
<string name="label_export_dir">Κατάλογος εξαγωγής</string>
<string name="label_not_found">δε βρέθηκε</string>
<string name="label_ignoreOutOfRange">Αγνόησε δεδομένα εκτός εύρους</string>
<string name="label_initial_weight">Αρχικό βάρος</string>
<string name="label_goal_line">Γραμμή στόχου</string>
@@ -149,13 +149,13 @@
<string name="customactivityoncrash_error_activity_close_app">Κλείσε την εφαρμογή</string>
<string name="customactivityoncrash_error_activity_error_details">Λεπτομέρειες σφάλματος</string>
<string name="customactivityoncrash_error_activity_error_details_title">Λεπτομέρειες σφάλματος</string>
<string name="customactivityoncrash_error_activity_error_details_close">Κλείσε</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Αντέγραψε στο πρόχειρο</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Αντεγράφη στο πρόχειρο</string>
<string name="customactivityoncrash_error_activity_error_details_close">Κλείσιμο</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Αντιγραφή στο πρόχειρο</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Αντιγράφηκε στο πρόχειρο</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Πληροφορίες σφάλματος</string>
<string name="toggle_expand">Μετέβαλε επέκταση</string>
<string name="edit">Επεξεργάσου</string>
<string name="save">Αποθήκευσε</string>
<string name="toggle_expand">Εναλλαγή επέκτασης</string>
<string name="edit">Επεξεργασία</string>
<string name="save">Αποθήκευση</string>
<string name="label_month_view">Μηνιαία προβολή</string>
<string name="label_weeks_view">Εβδομαδιαία προβολή</string>
<string name="permission_not_granted">Δεν χορηγήθηκε άδεια</string>
@@ -186,7 +186,7 @@
<string name="label_click_to_help_add_support">Πατήστε εδώ για να βοηθήσετε, προσθέσετε υποστήριξη</string>
<string name="label_development">Ανάπτυξη</string>
<string name="label_debug_log">Αποθήκευσε καταγραφή αποσφαλμάτωσης στο αρχείο</string>
<string name="label_your_bluetooth_scale">Η ζυγαριά σας με δυνατότητα Bluetooth</string>
<string name="label_your_bluetooth_scale">Αναζήτηση ζυγαριάς Bluetooth</string>
<string name="label_add_measurement">Προσθήκη μέτρησης</string>
<string name="trisa_scale_not_paired">Αυτή η ζυγαριά δεν έχει ζευγαρωθεί!
\n
@@ -233,6 +233,66 @@
<string name="label_is_on_right_axis">Είναι στον δεξιό άξονα</string>
<string name="label_upgrade_to_openScale_pro">Παρακαλώ αναβαθμίστε σε openScale pro για υποστήριξη Bluetooth</string>
<string name="action_donation">Δωρεά</string>
<string name="info_scale_low_battery">Χαμηλό επίπεδο μπαταρίας (%d%%), παρακαλώ αλλάξτε τις μπαταρίες της ζυγαριάς</string>
<string name="info_bluetooth_connection_error_scale_offline">Δεν μπορεί να γίνει σύνδεση με την ζυγαριά, παρακαλώ σιγουρευτείτε ότι η ζυγαριά είναι αναμμένη</string>
<string name="info_scale_low_battery">Χαμηλό επίπεδο μπαταρίας (%d%%), παρακαλώ φορτίστε ή αλλάξτε τις μπαταρίες της ζυγαριάς</string>
<string name="info_bluetooth_connection_error_scale_offline">Δεν μπορεί να γίνει σύνδεση με την ζυγαριά, παρακαλώ σιγουρευτείτε ότι είναι αναμμένη.</string>
<string name="label_slide_welcome_main_text">Καταγραφέας βάρους και σωματικών μετρήσεων ανοιχτού λογισμικού, με υποστήριξη ζευγαριών Bluetooth.</string>
<string name="app_intro_done_button">Ολοκληρώθηκε</string>
<string name="app_intro_back_button">Πίσω</string>
<string name="app_intro_next_button">Επόμενο</string>
<string name="app_intro_skip_button">Παράλειψη</string>
<string name="label_slide_support_top_text">Βοηθήστε στην βελτίωση του openScale.</string>
<string name="label_slide_metrics_top_text">Καταγραφή και ανάλυση των σωματικών μετρήσεων σου.</string>
<string name="label_slide_bluetooth_top_text">Υποστηρίζονται διάφορες ζυγαριές Bluetooth από διαφορετικούς κατασκευαστές.</string>
<string name="label_slide_opensource_top_text">Είναι λογισμικό ανοιχτού κώδικα με υψηλό βαθμό διαφάνειας.</string>
<string name="label_slide_user_top_text">Υποστηρίζονται πολλαπλοί χρήστες.</string>
<string name="label_slide_privacy_top_text">Προστατέψτε τα προσωπικά σας δεδομένα υγείας.</string>
<string name="label_slide_welcome_top_text">Καλώς ήρθατε
\nστο openScale</string>
<string name="amputation_level_leg">Μηρός</string>
<string name="amputation_level_lower_leg_foot">Χαμηλότερος μηρός και πόδι</string>
<string name="amputation_level_foot">Πόδι</string>
<string name="amputation_level_arm">Χέρι</string>
<string name="amputation_level_forearm_hand">Βραχίονας και μηρός</string>
<string name="amputation_level_hand">Χέρι</string>
<string name="amputation_level_none">Χωρίς ακρωτηριασμό</string>
<string name="label_trend">Τάση</string>
<string name="label_prediction">Πρόβλεψη</string>
<string name="label_trend_line">Γραμμή τάσης</string>
<string name="info_assisted_weighing_choose_reference_user">Επιλέξτε τον χρήστη αναφοράς του οποίου το βάρος αφαιρείται</string>
<string name="info_assisted_weighing_no_reference_user">Δεν υπάρχει διαθέσιμος χρήστης αναφοράς, παρακαλώ δημιουργήσετε έναν</string>
<string name="info_assisted_weighing_old_reference_measurement">Προειδοποίηση: η τελευταία μέτρηση αναφοράς είναι παλαιότερη από μία μέρα</string>
<string name="info_assisted_weighing_no_reference_measurements">Ο αναφερόμενος χρήστης δεν έχει μετρήσεις</string>
<string name="label_amputation_right">Ακρωτηριασμός δεξιά</string>
<string name="label_amputation_left">Ακρωτηριασμός αριστερά</string>
<string name="label_amputation">Ακρωτηριασμός</string>
<string name="label_assisted_weighing">Υποβοηθούμενη ζύγιση</string>
<string name="label_age">Ηλικία</string>
<string name="label_empty">κενό</string>
<string name="info_enter_consent_code_for_scale_user">Εισαγωγή PIN/κώδικα συναίνεσης, για τον χρήστη %s της ζυγαριάς</string>
<string name="label_slide_privacy_main_text">Το openScale δεν στέλνει οποιαδήποτε δεδομένα στο cloud και το ότι δεν έχει άδεια πρόσβασης στο διαδίκτυο είναι ισχυρή εγγύηση.
\n
\nΑν θέλετε να συγχρονίσετε το βάρος σας στο GoogleFit ή με MQTT, μπορείτε να εγκαταστήσετε το \u0020<a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_opensource_main_text">Το openScale αδειοδοτείται υπό την <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
\n
\nΌλες οι σωματικές μετρήσεις είναι διαφανείς και δεν γίνεται κάποια μεταφορά δεδομένων ή ταυτοποίηση χρήστη.
\n
\nΜπορείτε να βρείτε τον πλήρη κώδικα στο <a href="https://github.com/oliexdev/openScale">GitHub</a>. Πείσε τον εαυτό σου για το openScale βλέποντας το.</string>
<string name="label_slide_bluetooth_main_text">Το openScale έχει ενσωματωμένη υποστήριξη για αρκετές ζυγαριές Bluetooth από διαφορετικούς κατασκευαστές.
\n
\nΒελτιώνουμε συνεχώς και επεκτείνουμε την υποστήριξη ζυγαριών. Μπορείτε να βρείτε την πλήρη λίστα και το επίπεδο υποστήριξης για κάθε ζυγαριά στο <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_slide_support_main_text">Το openScale σας χρειάζεται. Δημιουργήστε ένα <a href="https://github.com/oliexdev/openScale/issues">new issue</a> αν βρείτε κάποιο σφάλμα, έχετε μια ιδέα, ερώτηση, ή θέλετε να βοηθήσετε στην υποστήριξη της Bluetooth ζυγαριάς σας.
\n
\nΠαρακινήστε τον δημιουργό αυτού του έργου σε περαιτέρω ανάπτυξη και συνεχιζόμενη συντήρηση, δίνοντας στο openScale θετική βαθμολογία στο <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> ή ένα αστέρι στο <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nΗ υποστήριξή σας και τα θετικά σας σχόλια εκτιμούνται ιδιαίτερα. Σας ευχαριστώ.</string>
<string name="info_create_new_user_on_scale">Δημιουργία νέου χρήστη στην ζυγαριά.</string>
<string name="info_select_scale_user">Επιλογή χρήστη ζυγαριάς</string>
<string name="label_slide_user_main_text">Το openScale δεν απαιτεί την δημιουργία διαδικτυακού λογαριασμού.
\n
\nΤα ανθρωπομετρικά στοιχεία υπολογίζονται από τις μετρήσεις.</string>
<string name="label_slide_metrics_main_text">Το openScale υποστηρίζει πάνω από 22 μετρήσεις σώματος.
\n
\nΡυθμίστε το να δείχνει αυτές που σας ενδιαφέρουν..
\n
\n Αν το body fat, body water και lean body mass λείπει, μπορεί να προσδιοριστεί με βάση <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">επιστημονικές δημοσιεύσεις</a>.</string>
</resources>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_overview">Superrigardo</string>
<string name="title_graph">Diagramo</string>
<string name="title_table">Tabelo</string>
<string name="title_statistics">Statistikoj</string>
<string name="title_users">Uzantoj</string>
<string name="title_measurements">Mezuroj</string>
<string name="title_about">Pri</string>
<string name="title_general">Ĉefaj</string>
<string name="action_settings">Agordoj</string>
<string name="action_bluetooth_status">Bludenta statuso</string>
<string name="action_donation">Donaci</string>
<string name="open_drawer">malfermi</string>
<string name="close_drawer">fermi</string>
<string name="label_cancel">Nuligi</string>
<string name="label_ok">Bone</string>
<string name="label_yes">Jes</string>
<string name="label_no">Ne</string>
<string name="label_delete">Forigi</string>
<string name="label_empty">senenhava</string>
<string name="label_add_user">Aldoni uzanton</string>
<string name="label_add_measurement">Aldoni mezuron</string>
<string name="label_share">Konigi</string>
<string name="label_weight">Pezo</string>
<string name="label_bmi">Korpomasa indico (KMI/BMI)</string>
<string name="label_bmr">Baza metabolo (BM/BMR)</string>
<string name="label_fat">Korpograso</string>
<string name="label_muscle">Muskoloj</string>
<string name="label_comment">Komento</string>
<string name="label_bone">Ostomaso</string>
<plurals name="label_days">
<item quantity="one">tago %d</item>
<item quantity="other">tagoj %d</item>
</plurals>
<string name="label_goal_date_is">Cela dato:</string>
<string name="label_date">Dato</string>
<string name="label_time">Tempo</string>
<string name="label_birthday">Naskiĝtago</string>
<string name="label_user_name">Nomo</string>
<string name="label_height">Alto</string>
<string name="label_gender">Genro</string>
<string name="label_amputation">Amputadoj</string>
<string name="label_male">Iĉa</string>
<string name="label_goal_date">Cela dato</string>
<string name="label_title_user">uzanto</string>
<string name="label_title_last_measurement">lasta mezuro</string>
<string name="label_title_goal">Celo</string>
<string name="label_import">Enporti</string>
<string name="label_export">Elporti</string>
<string name="label_delete_all">Forigi ĉiujn</string>
<string name="error_value_required">Valoro devigia</string>
<string name="error_value_range">Valoro ekstervarieja</string>
<string name="error_exporting">Ne povis elporti</string>
<string name="error_importing">Ne povis enporti</string>
<string name="error_user_name_required">Nomo deviga</string>
<string name="error_initial_weight_required">Komenca pezeco deviga</string>
<string name="error_goal_weight_required">Cela pezeco deviga</string>
<string name="info_data_deleted">Elemento forigita</string>
<string name="info_data_all_deleted">Ĉiuj elementoj forigitaj</string>
<string name="info_data_exported">Elportita al</string>
<string name="info_data_imported">Enportita el</string>
<string name="info_enter_value_in">Valoro en</string>
<string name="info_enter_comment">Nedeviga komento</string>
<string name="info_is_not_enable">malŝaltita</string>
<string name="info_is_not_available">ne disponebla</string>
<string name="info_bluetooth_try_connection">Konektadas al:</string>
<string name="info_bluetooth_connection_lost">Bludenta konekto perdita</string>
<string name="info_bluetooth_no_device">Neniu Bludenta aparato trovita</string>
<string name="info_bluetooth_no_device_set">Elektu Bludentan aparaton</string>
<string name="info_bluetooth_connection_successful">Konektita</string>
<string name="info_enter_user_name">Via nomo</string>
<string name="info_no_selected_user">Neniu uzanto ekzistas. Bonvole kreu iun en la agordoj.</string>
<string name="info_no_evaluation_available">Ne povas difini la valoron</string>
<string name="question_really_delete">Forigi elementon\?</string>
<string name="question_really_delete_all">Forigi ĉiujn elementojn de ĉiuj uzantoj\?</string>
<string name="question_really_delete_user">Forigi uzanton\?</string>
<string name="label_bluetooth_title">Bludento</string>
<string name="label_enable_legend">Legendo de grafo</string>
<string name="label_enable_yaxis">Y-akso</string>
<string name="label_enable_labels">Data etikedo</string>
<string name="label_website">Retejo</string>
<string name="label_license">Permesilo</string>
<string name="label_automatic">aŭtomate</string>
<string name="label_theme">Etoso</string>
<string name="label_reminder">Memorigo</string>
<string name="label_reminder_weekdays">Tagoj</string>
<string name="label_reminder_time">Tempo</string>
<string name="label_reminder_notify_text">Sciiga teksto</string>
<string name="default_value_reminder_notify_text">Tempo por vi pesi sin</string>
<string name="info_on_date">je</string>
<string name="Monday">Lundo</string>
<string name="Saturday">Sabato</string>
<string name="Sunday">Dimanĉo</string>
<string name="label_backup">Savkopio</string>
<string name="label_export_dir">Dosierujo elportota</string>
<string name="label_not_found">ne trovita</string>
<string name="label_goal_line">Cela linio</string>
<string name="label_age">Aĝo</string>
<string name="app_intro_done_button">Farita</string>
<string name="app_intro_back_button">Reen</string>
<string name="app_intro_next_button">Sekve</string>
<string name="app_intro_skip_button">Preterpasi</string>
<string name="label_slide_bluetooth_top_text">Diversaj Bludentaj pesiloj de diversaj firmaoj estas subtenata.</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] aldonita al %4$s</string>
<string name="label_female">Ina</string>
<string name="label_goal_weight">Cela pezo</string>
<string name="error_height_required">Alteco deviga</string>
<string name="info_is_visible">videblas</string>
<string name="label_importBackup">Enporti savkopion</string>
<string name="info_bluetooth_connection_disconnected">Bludenta konekto fermita</string>
<string name="info_is_not_visible">nevideblas</string>
<string name="info_is_enable">ŝaltita</string>
<string name="Wednesday">Merkredo</string>
<string name="Thursday">Ĵaŭdo</string>
<string name="label_bt_device_no_support">aparato ne subtenita</string>
<string name="Tuesday">Mardo</string>
<string name="label_exportBackup">Elporti savkopion</string>
<string name="Friday">Vendredo</string>
<string name="label_initial_weight">Komenca pezeco</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Informoj pri eraro</string>
<string name="label_waist">Talia perimetro</string>
<string name="label_hip">Koksa perimetro</string>
<string name="label_whtr">Rilatumo de talia perimetro al alto</string>
<string name="label_whr">Rilatumo de talia perimetro al koksa perimetro</string>
<string name="label_weight_difference">Diferenco de pezo</string>
<string name="label_amputation_left">Maldekstra amputo</string>
<string name="label_amputation_right">Dekstra amputo</string>
<string name="label_enable_points">Datenpunkto</string>
<string name="info_bluetooth_init">Praŝargi Bludentan aparaton</string>
<string name="info_bluetooth_connection_error">Neatendita Bludenta eraro</string>
<string name="label_help">Helpo</string>
<string name="label_feedback_message_yes">Jes</string>
<string name="label_feedback_message_positive">Bone</string>
<string name="label_feedback_message_negative">Ne, sed dankon</string>
<string name="customactivityoncrash_error_activity_error_details">Detaloj pri eraro</string>
<string name="customactivityoncrash_error_activity_error_details_title">Detaloj pri eraro</string>
<string name="customactivityoncrash_error_activity_error_details_close">Fermi</string>
<string name="edit">Modifi</string>
<string name="save">Konservi</string>
<string name="label_feedback_message_enjoying">Ĉu ĝuante openScale\?</string>
<string name="customactivityoncrash_error_activity_restart_app">Relanĉi programon</string>
<string name="label_day_view">Taga vido</string>
<string name="label_month_view">Monata vido</string>
<string name="label_weeks_view">Semajna vido</string>
<string name="label_year_view">Jara vido</string>
<string name="customactivityoncrash_error_activity_close_app">Fermi programon</string>
<string name="info_measuring">Mezurante pezon: %.2f</string>
<string name="theme_light">Hela</string>
<string name="theme_dark">Malhela</string>
<string name="label_contribute_translation">Kontribui tradukon</string>
<string name="label_daily">ĉiutage</string>
<string name="label_weekly">ĉiusemajne</string>
<string name="label_monthly">ĉiumonate</string>
<string name="label_select_user">Elekti uzanton</string>
<string name="label_percent">Procento</string>
<string name="label_language">Lingvo</string>
<string name="label_calories">Kalorioj</string>
<string name="amputation_level_hand">Mano</string>
<string name="amputation_level_lower_leg_foot">Kruro kaj piedo</string>
<string name="amputation_level_leg">Gambo</string>
<string name="label_slide_welcome_top_text">Bonvenon al
\nopenScale</string>
<string name="amputation_level_foot">Piedo</string>
<string name="amputation_level_arm">Brako</string>
<string name="amputation_level_forearm_hand">Antaŭbrako kaj gambo</string>
<string name="amputation_level_none">Ne amputita</string>
<string name="label_slide_support_top_text">Helpu plibonigi openScale.</string>
<string name="permission_bluetooth_info_title">Informo</string>
</resources>

View File

@@ -158,7 +158,7 @@
<string name="permission_not_granted">Permiso no otorgado</string>
<string name="permission_bluetooth_info">Se requiere permiso para ubicar dispositivos Bluetooth. Este permiso puede ser revocado después de que se encontró el dispositivo.</string>
<string name="permission_bluetooth_info_title">Información</string>
<string name="label_share_subject">Exportar a CSV (%s)</string>
<string name="label_share_subject">openScale datos exportar (%s)</string>
<string name="label_mergeWithLastMeasurement">Unir con la última medición</string>
<string name="label_weeks_view">Vista semanal</string>
<string name="label_next">Siguiente</string>
@@ -200,7 +200,7 @@
<string name="label_biceps">Circunferencia del bíceps</string>
<string name="label_neck">Circunferencia del cuello</string>
<string name="label_fat_caliper">Calibrador de grasa corporal</string>
<string name="label_caliper1_male">Pliegues del pecho</string>
<string name="label_caliper1_male">Pliegues del torso</string>
<string name="label_caliper2_male">Pliegues abdominales</string>
<string name="label_caliper3_male">Pliegues del muslo</string>
<string name="label_caliper1_female">Pliegues del tríceps</string>
@@ -219,17 +219,17 @@
<string name="trisa_scale_pairing_succeeded">¡Emparejamiento exitoso!
\n
\nVuelva a conectar para obtener datos de mediciones.</string>
<string name="label_tdee">Gasto energético total diario (GET)</string>
<string name="label_tdee">Gasto energético total diario</string>
<string name="info_bluetooth_no_device_retrying">No se pudo establecer la conexión; reintentando…</string>
<string name="info_bluetooth_connection_disconnected">Conexión Bluetooth cerrada</string>
<string name="label_enable_legend">Leyenda del gráfico</string>
<string name="label_enable_yaxis">Eje Y</string>
<string name="info_step_on_scale">Súbase con los pies descalzos en la báscula</string>
<string name="info_step_on_scale">Por favor, párese descalzo en la balanza</string>
<string name="label_day_view">Vista diaria</string>
<string name="label_year_view">Vista anual</string>
<string name="label_measurement_bar">Barra de medición</string>
<string name="permission_location_service_info">Otorgue permiso para acceso a la ubicación, en la configuración de Android, para buscar dispositivos Bluetooth. Podrá revocarlo después.</string>
<string name="label_is_on_right_axis">Está en el eje de la derecha</string>
<string name="label_measurement_bar">Barra de medida</string>
<string name="permission_location_service_info">Otorgue acceso a la ubicación en la configuración de Android para buscar dispositivos Bluetooth. Opcionalmente puede revocarlo después.</string>
<string name="label_is_on_right_axis">Está en el eje derecho</string>
<string name="label_calories">Calorías</string>
<string name="label_upgrade_to_openScale_pro">Cambie a la versión Pro de openScale para obtener la prestación de Bluetooth</string>
<string name="action_donation">Donación</string>
@@ -283,10 +283,16 @@
<string name="amputation_level_none">Sin amputación</string>
<string name="info_assisted_weighing_choose_reference_user">Elija el usuario de referencia al que se le reste el peso</string>
<string name="info_assisted_weighing_no_reference_user">No hay un usuario de referencia disponible, por favor cree uno</string>
<string name="info_assisted_weighing_old_reference_measurement">Advertencia la última medida de referencia es más vieja que un día</string>
<string name="info_assisted_weighing_old_reference_measurement">Advertencia: la última medida de referencia es más vieja que un día</string>
<string name="info_assisted_weighing_no_reference_measurements">El usuario de referencia no tiene medidas</string>
<string name="label_amputation_right">Amputación derecha</string>
<string name="label_amputation_left">Amputación izquierda</string>
<string name="label_amputation">Amputación</string>
<string name="label_assisted_weighing">Pesaje asistido</string>
<string name="label_trend">Tendencia</string>
<string name="label_prediction">Proyección</string>
<string name="label_trend_line">Línea de tendencia</string>
<string name="info_enter_consent_code_for_scale_user">Introduce PIN/código de consentimiento para el usuario %s</string>
<string name="info_create_new_user_on_scale">Crea nuevo usuario en la escala.</string>
<string name="info_select_scale_user">Selecciona usuario</string>
</resources>

View File

@@ -200,4 +200,9 @@
<string name="action_settings">Asetukset</string>
<string name="title_measurements">Mittaukset</string>
<string name="title_users">Käyttäjät</string>
<string name="error_value_range">Arvo on rajojen ulkopuolella</string>
<string name="label_goal_date_is">Tavoitepäivä:</string>
<string name="label_weight_difference">Painonmuutos</string>
<string name="label_bone">Luumassa</string>
<string name="label_muscle">Lihaskudos</string>
</resources>

View File

@@ -144,7 +144,7 @@
<string name="label_estimate_measurement_summary">Basé sur le poids, la taille, l\'âge, le sexe, etc.</string>
<string name="open_drawer">ouvrir</string>
<string name="close_drawer">fermer</string>
<string name="label_share_subject">Exporter comme fichier CSV (%s)</string>
<string name="label_share_subject">Exportation de données openScale (%s)</string>
<string name="label_lbm">Masse sans graisse</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] ajouté pour %4$s</string>
<string name="info_new_data_duplicated">Une mesure à la même date et heure existe déjà</string>
@@ -160,8 +160,8 @@
<string name="label_goal_line">Ligne pour l\'objectif</string>
<string name="label_feedback_message_enjoying">Êtes-vous satisfait d\'openScale ?</string>
<string name="label_feedback_message_rate_app">Laisser une évaluation sur Google Play ou GitHub ?</string>
<string name="error_max_scale_users">Le nombre maximum d\'utilisateurs de la balance atteint</string>
<string name="info_step_on_scale_for_reference">Montez svp sur la balance, pieds nus, pour les mesures de référence</string>
<string name="error_max_scale_users">Nombre maximal d\'utilisateurs de la balance atteint</string>
<string name="info_step_on_scale_for_reference">Veuillez monter sur la balance, pieds nus, pour les mesures de référence</string>
<string name="info_measuring">En train de mesurer le poids : %.2f</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Une erreur est apparue.
\n
@@ -256,7 +256,7 @@
\n
\nLes métriques corporelles sont calculées à partir de mesures.</string>
<string name="label_empty">vide</string>
<string name="label_slide_privacy_main_text">openScale n\'envoie aucune donnée dans le cloud et le fait de ne pas avoir la permission d\'accéder à l\'internet en est une garantie solide.
<string name="label_slide_privacy_main_text">openScale n\'envoie aucune donnée dans le nuage et le fait de ne pas avoir la permission d\'accéder à l\'internet en est une garantie solide.
\n
\nSi vous voulez vraiment synchroniser votre poids avec GoogleFit ou avec MQTT, vous pouvez installer <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_privacy_top_text">Protégez vos données personnelles de santé.</string>
@@ -283,10 +283,16 @@
<string name="amputation_level_hand">Main</string>
<string name="info_assisted_weighing_choose_reference_user">Choisissez l\'utilisateur de référence auquel le poids est soustrait</string>
<string name="info_assisted_weighing_no_reference_user">Pas d\'utilisateur de référence disponible, veuillez en créer un</string>
<string name="info_assisted_weighing_old_reference_measurement">Attention, la dernière mesure de référence date de plus d\'un jour</string>
<string name="info_assisted_weighing_old_reference_measurement">Attention : la dernière mesure de référence date de plus d\'un jour</string>
<string name="info_assisted_weighing_no_reference_measurements">L\'utilisateur de référence n\'a pas de mesures</string>
<string name="label_amputation_right">Amputation droite</string>
<string name="label_amputation_left">Amputation gauche</string>
<string name="label_amputation">Amputation</string>
<string name="label_assisted_weighing">Pesée assistée</string>
<string name="label_trend">Tendance</string>
<string name="label_prediction">Prédiction</string>
<string name="label_trend_line">Ligne de tendances</string>
<string name="info_create_new_user_on_scale">Créer un nouvel utilisateur sur la balance.</string>
<string name="info_enter_consent_code_for_scale_user">Entrez le NIP/code de consentement de l\'utilisateur de la balance %s</string>
<string name="info_select_scale_user">Sélectionner un utilisateur</string>
</resources>

View File

@@ -155,7 +155,7 @@
<string name="label_month_view">Vista de mes</string>
<string name="label_weeks_view">Vista de semana</string>
<string name="permission_not_granted">Permiso non concedido</string>
<string name="permission_bluetooth_info">Necesítase permiso de localización para para buscar dispositivos Bluetooth. Pódese retirar unha vez atopado o dispositivo.</string>
<string name="permission_bluetooth_info">Necesítase permiso de localización para buscar dispositivos Bluetooth. Pódese retirar unha vez atopado o dispositivo.</string>
<string name="permission_bluetooth_info_title">Información</string>
<string name="label_next">Seguinte</string>
<string name="label_long_press_drag_reorder">Manteña pulsada a medida e arrastre para reordenar</string>

View File

@@ -19,8 +19,8 @@
<string name="label_delete">Izbriši</string>
<string name="label_add_user">Dodaj korisnika</string>
<string name="label_add_measurement">Dodaj mjerenje</string>
<string name="label_share">Podijeli</string>
<string name="label_share_subject">CSV izvoz (%s)</string>
<string name="label_share">Dijeli</string>
<string name="label_share_subject">Izvoz openScale podataka (%s)</string>
<string name="label_weight">Težina</string>
<string name="label_bmi">Indeks tjelesne mase (BMI)</string>
<string name="label_bmr">Bazalni metabolizam (BMR)</string>
@@ -88,7 +88,7 @@
<string name="info_bluetooth_connection_successful">Povezano</string>
<string name="info_bluetooth_init">Inicijaliziraj Bluetooth uređaj</string>
<string name="info_bluetooth_connection_error">Neočekivana Bluetooth greška</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] do %4$s dodan</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] dodan do %4$s</string>
<string name="info_new_data_duplicated">mjerenje s istim datumom i vremenom već postoji</string>
<string name="info_enter_user_name">Tvoje ime</string>
<string name="info_no_selected_user">Ne postoji niti jedan korisnik. Stvori jednog korisnika u postavkama.</string>
@@ -131,7 +131,7 @@
<string name="label_not_found">nije pronađeno</string>
<string name="label_ignoreOutOfRange">Zanemari podatke izvan granica</string>
<string name="label_initial_weight">Početna težina</string>
<string name="label_goal_line">Ciljana linija</string>
<string name="label_goal_line">Smjer cilja</string>
<string name="label_help">Pomoć</string>
<string name="label_feedback_message_enjoying">Sviđa ti se openScale\?</string>
<string name="label_feedback_message_rate_app">Kako bi bilo, da ocijeniš program na Google Play-u ili GitHub-u\?</string>
@@ -284,10 +284,16 @@
<string name="amputation_level_none">Bez amputacije</string>
<string name="info_assisted_weighing_choose_reference_user">Odaberi referentnog korisnika čija se težina oduzima</string>
<string name="info_assisted_weighing_no_reference_user">Nema referentnog korisnika, stvori ga</string>
<string name="info_assisted_weighing_old_reference_measurement">Upozorenje, da je zadnje referentno mjerenje starije je od jednog dana</string>
<string name="info_assisted_weighing_old_reference_measurement">Upozorenje: zadnje referentno mjerenje starije je od jednog dana</string>
<string name="info_assisted_weighing_no_reference_measurements">Referentni korisnik nema mjerenja</string>
<string name="label_amputation_right">Amputacija desno</string>
<string name="label_amputation_left">Amputacija lijevo</string>
<string name="label_amputation">Amputacija</string>
<string name="label_assisted_weighing">Potpomognuto vaganje</string>
<string name="label_trend_line">Smjer trenda</string>
<string name="label_trend">Trend</string>
<string name="label_prediction">Predviđanje</string>
<string name="info_create_new_user_on_scale">Kreiraj novog korisnika vage.</string>
<string name="info_select_scale_user">Odaberi korisnika vage</string>
<string name="info_enter_consent_code_for_scale_user">Upiši PIN/kod za korisnika vage %s</string>
</resources>

View File

@@ -20,11 +20,11 @@
<string name="label_add_user">Új felhasználó</string>
<string name="label_add_measurement">Új mérés</string>
<string name="label_share">Megosztás</string>
<string name="label_share_subject">CSV adat exportálás (%s)</string>
<string name="label_share_subject">openScale adat exportálás (%s)</string>
<string name="label_weight">Súly</string>
<string name="label_bmi">Test tömeg index (BMI)</string>
<string name="label_fat">Test zsír</string>
<string name="label_water">Test víz</string>
<string name="label_water">Testvíz</string>
<string name="label_muscle">Izom</string>
<string name="label_waist">Derék kerülete</string>
<string name="label_hip">Csípő kerület</string>
@@ -61,16 +61,16 @@
<string name="label_delete_all">Töröld mind</string>
<string name="error_value_required">Érték szükséges</string>
<string name="error_value_range">Érték tartományon kívül</string>
<string name="error_exporting">Hiba exportáláskor</string>
<string name="error_importing">Hiba importáláskor</string>
<string name="error_user_name_required">Hiba: Név szükséges</string>
<string name="error_height_required">Hiba: Magasság szükséges</string>
<string name="error_initial_weight_required">Hiba: Kezdő súly szükséges</string>
<string name="error_goal_weight_required">Hiba: Cél súly szükséges</string>
<string name="error_exporting">Nem sikerült exportálni</string>
<string name="error_importing">Nem sikerült importálni</string>
<string name="error_user_name_required">Név szükséges</string>
<string name="error_height_required">Magasság szükséges</string>
<string name="error_initial_weight_required">Kezdő súly szükséges</string>
<string name="error_goal_weight_required">Cél súly szükséges</string>
<string name="info_data_deleted">Bejegyzés törölve</string>
<string name="info_data_all_deleted">Minden bejegyzés törölve</string>
<string name="info_data_exported">"Exportálva ide "</string>
<string name="info_data_imported">"Importálva innen "</string>
<string name="info_data_exported">Exportálva ide</string>
<string name="info_data_imported">Importálva innen</string>
<string name="info_enter_comment">Opcionális megjegyzés</string>
<string name="info_is_visible">látható</string>
<string name="info_is_enable">engedélyezve</string>
@@ -78,9 +78,9 @@
<string name="info_is_not_available">nem elérhető</string>
<string name="info_bluetooth_try_connection">Kapcsolódás:</string>
<string name="info_bluetooth_connection_lost">Bluetooth kapcsolat megszakadt</string>
<string name="info_bluetooth_no_device">Nem találtam Bluetooth eszközt</string>
<string name="info_bluetooth_no_device_set">Nincs Bluetooth eszköz kiválasztva</string>
<string name="info_bluetooth_connection_successful">Kapcsolat létrejött</string>
<string name="info_bluetooth_no_device">Nem található Bluetooth eszköz</string>
<string name="info_bluetooth_no_device_set">Bluetooth eszköz kiválasztása</string>
<string name="info_bluetooth_connection_successful">Kapcsolódva</string>
<string name="info_bluetooth_init">Bluetooth eszköz inicializálása</string>
<string name="info_bluetooth_connection_error">Váratlan Bluetooth hiba</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] ide %4$s hozzáadva</string>
@@ -93,8 +93,8 @@
<string name="question_really_delete_user">Felhasználó törlése\?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Indításkor csatlakozás mérleghez</string>
<string name="label_bluetooth_searching">Bluetooth mérlegének keresése</string>
<string name="label_bluetooth_searching_finished">Keresés befejeződött</string>
<string name="label_bluetooth_searching">Bluetooth mérleg keresése</string>
<string name="label_bluetooth_searching_finished">A keresés befejeződött</string>
<string name="label_enable_labels">Adat címke</string>
<string name="label_enable_points">Adat pont</string>
<string name="label_delete_confirmation">Törlési megerősítés</string>
@@ -185,15 +185,15 @@
\nhttps://github.com/oliexdev/openScale/issues-on</string>
<string name="toggle_expand">Kattintson a megnyitáshoz</string>
<string name="permission_bluetooth_info">A Bluetooth-eszközök kereséséhez szükséges a tartózkodási hely hozzáférése. A készülék megtalálása után visszavonható.</string>
<string name="permission_read_write_data_description">"openScale adatok olvasása/írása, beleértve a felhasználói infót és minden mentett mérési eredményt "</string>
<string name="permission_read_write_data_label">"OpenScale adatokat olvasása/írása "</string>
<string name="permission_read_write_data_description">openScale adatok olvasása/írása, beleértve a felhasználói infót és minden mentett mérési eredményt</string>
<string name="permission_read_write_data_label">OpenScale adatokat olvasása/írása</string>
<string name="label_long_press_drag_reorder">Hosszan nyomva a mérési eredményt átrendezheti</string>
<string name="label_measurement_in_percent">Mérés %-ban</string>
<string name="label_estimate_measurement">Mérés becslése</string>
<string name="label_add_or_fix_translation">Új hozzáadása vagy meglévő javítása</string>
<string name="label_estimate_measurement_summary">Súlyon, magasságon, koron, nemen alapulva, stb.</string>
<string name="label_scale_not_supported">Nincs a mérlege támogatva\?</string>
<string name="label_click_to_help_add_support">Kattintson ide, segíteni a támogatás hozzáadáshoz</string>
<string name="label_click_to_help_add_support">Kattintson ide, hogy segítsen támogatást nyújtani hozzá</string>
<string name="label_development">Fejlesztés</string>
<string name="label_debug_log">Debug-napló fájlba mentése</string>
<string name="label_your_bluetooth_scale">Az Ön Bluetooth mérlege</string>
@@ -219,7 +219,7 @@
<string name="activity_level_moderate">Mérsékelt</string>
<string name="activity_level_heavy">Élénk</string>
<string name="activity_level_extreme">Extrém</string>
<string name="label_tdee">Összes napi energia kiadás (TDEE)</string>
<string name="label_tdee">Teljes napi energiafelhasználás (TDEE)</string>
<string name="label_calories">Kalória</string>
<string name="info_bluetooth_no_device_retrying">Kapcsolat nem hozható létre, újra…</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth kapcsolat lezárva</string>
@@ -234,4 +234,65 @@
<string name="label_upgrade_to_openScale_pro">Kérem frissítsen az openScale Pro-ra a Bluetooth támogatáshoz</string>
<string name="action_donation">Támogatás</string>
<string name="label_empty">üres</string>
<string name="label_slide_welcome_main_text">Nyílt forráskódú szoftver, súly- és testmérések nyomon követése, Bluetooth mérleg támogatással.</string>
<string name="label_slide_opensource_main_text">Az openScale a <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a> licenc alatt áll.
\n
\nAz összes testszámítás átlátható, és nem történik rejtett adatátviteli funkció vagy felhasználói azonosítás.
\n
\nA teljes forráskód megtalálható a <a href="https://github.com/oliexdev/openScale">GitHub</a> oldalon. Győződjön meg róla az openScale áttekintésével.</string>
<string name="label_amputation">Amputáció</string>
<string name="info_scale_low_battery">Alacsony töltöttségi szint (%d%%), kérjük, töltse fel vagy cserélje ki a mérleg elemeit</string>
<string name="label_amputation_right">Amputáció jobb</string>
<string name="info_select_scale_user">Felhasználó kiválasztása</string>
<string name="info_enter_consent_code_for_scale_user">Adja meg a PIN kódot/hozzájárulási kódot a mérlegelő %s felhasználóhoz</string>
<string name="app_intro_next_button">Tovább</string>
<string name="amputation_level_none">Nincs amputáció</string>
<string name="amputation_level_hand">Kéz</string>
<string name="label_slide_privacy_main_text">Az openScale nem küld semmilyen adatot felhőbe, és az internet-hozzáférés engedélyének hiánya erős garancia erre.
\n
\nHa mindenképpen szinkronizálni szeretné a súlyát a GoogleFit vagy az MQTT segítségével, akkor telepítheti a <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a> programot.</string>
<string name="label_slide_bluetooth_main_text">Az openScale beépített támogatással rendelkezik számos, különböző gyártótól származó Bluetooth mérleghez.
\n
\nFolyamatosan fejlesztjük és bővítjük a támogatott mérlegek körét. A teljes listát és az egyes mérlegek támogatási szintjét a <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a> oldalon találja.</string>
<string name="app_intro_skip_button">Kihagy</string>
<string name="label_slide_bluetooth_top_text">A különböző gyártók különböző Bluetooth mérlegei támogatva vannak.</string>
<string name="label_slide_metrics_main_text">Az openScale több mint 22 testmérést támogat.
\n
\nÚgy állíthatja be, hogy azokat mutassa, amelyek érdeklik.
\n
\nHa a testzsír, a testvíz és a sovány testtömeg nem áll rendelkezésre, az <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">tudományos publikációk</a> alapján megbecsülhető.</string>
<string name="label_slide_support_top_text">Segítsen az openScale fejlesztésében.</string>
<string name="app_intro_done_button">Kész</string>
<string name="info_create_new_user_on_scale">Új felhasználó létrehozása a mérleghez.</string>
<string name="label_trend_line">Trendvonal</string>
<string name="label_prediction">Előrejelzés</string>
<string name="label_trend">Trend</string>
<string name="amputation_level_leg">Láb</string>
<string name="app_intro_back_button">Vissza</string>
<string name="info_bluetooth_connection_error_scale_offline">Nem sikerült csatlakozni a mérleghez, kérjük, ellenőrizze, hogy be van-e kapcsolva.</string>
<string name="label_amputation_left">Amputáció bal</string>
<string name="label_age">Életkor</string>
<string name="amputation_level_forearm_hand">Alkar és lábszár</string>
<string name="amputation_level_arm">Kar</string>
<string name="amputation_level_foot">Láb</string>
<string name="amputation_level_lower_leg_foot">Alsó lábszár és lábfej</string>
<string name="label_slide_welcome_top_text">Üdvözöljük az
\nopenScale-ben</string>
<string name="label_slide_privacy_top_text">Személyes egészségügyi adatainak védelme.</string>
<string name="label_slide_user_top_text">Több felhasználó támogatása.</string>
<string name="label_slide_user_main_text">Az openScale nem igényli online fiók létrehozását.
\n
\nA testmérések a mérésekből lesznek kiszámítva.</string>
<string name="label_slide_opensource_top_text">Ez egy nyílt forráskódú szoftver, amely nagyfokú átláthatósággal rendelkezik.</string>
<string name="label_slide_metrics_top_text">Kövesse nyomon és elemezze testének mérőszámait.</string>
<string name="info_assisted_weighing_old_reference_measurement">Figyelmeztetés: az utolsó referenciamérés egy napnál régebbi</string>
<string name="info_assisted_weighing_no_reference_user">Nincs referencia felhasználó, kérjük, hozzon létre egyet</string>
<string name="info_assisted_weighing_choose_reference_user">Válassza ki a referenciafelhasználót, akitől a súlyt levonják</string>
<string name="info_assisted_weighing_no_reference_measurements">A referencia felhasználónak nincsenek mérései</string>
<string name="label_assisted_weighing">Rásegítéses tömegmérés</string>
<string name="label_slide_support_main_text">Az openScale-nek szüksége van Önre. Jelezzen problémát a <a href="https://github.com/oliexdev/openScale/issues">címen</a>, ha hibát talált, ötlete vagy kérdése van, vagy segíteni szeretne a Bluetooth mérlegelés támogatásában.
\n
\nMotiválja a projekt létrehozóját a további fejlesztésben és a folyamatos karbantartásban azzal, hogy pozitív értékelést ad az openScale-nek a <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> oldalon, vagy csillagot a <a href="https://github.com/oliexdev/openScale">GitHub</a> oldalon.
\n
\nTámogatását és pozitív visszajelzését nagyra értékeljük. Köszönjük.</string>
</resources>

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="theme_dark">Gelap</string>
<string name="customactivityoncrash_error_activity_error_details">Rincian galat</string>
<string name="customactivityoncrash_error_activity_close_app">Tutup aplikasi</string>
<string name="label_reminder_notify_text">Notifikasi teks</string>
<string name="label_reminder_time">Waktu</string>
<string name="label_reminder_weekdays">Hari</string>
<string name="label_reminder">Pengingat</string>
<string name="label_theme">Tema</string>
<string name="app_intro_done_button">Selesai</string>
<string name="app_intro_back_button">Kembali</string>
<string name="app_intro_next_button">Lanjut</string>
<string name="app_intro_skip_button">Lewat</string>
<string name="info_bluetooth_try_connection">Menghubungkan ke:</string>
<string name="info_is_enable">aktif</string>
<string name="info_is_not_enable">nonaktif</string>
<string name="info_is_not_visible">tidak terlihat</string>
<string name="info_is_visible">terlihat</string>
<string name="info_data_imported">Diimpor dari</string>
<string name="info_data_exported">Diekspor ke</string>
<string name="label_delete_all">Hapus semua</string>
<string name="label_export">Ekspor</string>
<string name="label_import">Impor</string>
<string name="label_title_goal">Target</string>
<string name="label_title_last_measurement">pengukuran terakhir</string>
<string name="label_title_user">pengguna</string>
<string name="label_goal_date">Target tanggal</string>
<string name="label_goal_weight">Target berat</string>
<string name="label_female">Perempuan</string>
<string name="label_male">Laki-laki</string>
<string name="label_gender">Jenis kelamin</string>
<string name="label_scale_unit">Skala unit</string>
<string name="label_height">Tinggi</string>
<string name="label_user_name">Nama</string>
<string name="label_birthday">Hari ulang tahun</string>
<string name="label_time">Waktu</string>
<string name="label_date">Tanggal</string>
<string name="label_days_left">Hari tersisa</string>
<string name="label_goal_date_is">Tanggal target:</string>
<string name="label_weight_difference">Perbedaan berat</string>
<string name="label_last_month">Rata-rata bulan lalu</string>
<string name="label_last_week">Rata-rata minggu lalu</string>
<plurals name="label_days">
<item quantity="other">hari %d</item>
</plurals>
<string name="label_age">Usia</string>
<string name="label_bone">Massa tulang</string>
<string name="label_whr">Rasio pinggang-pinggul</string>
<string name="label_whtr">Rasio pinggang-tinggi</string>
<string name="label_comment">Komentar</string>
<string name="label_hip">Lingkar pinggul</string>
<string name="label_waist">Lingkar pinggang</string>
<string name="label_lbm">Massa tubuh tanpa lemak</string>
<string name="label_muscle">Otot</string>
<string name="label_water">Total air tubuh</string>
<string name="label_fat">Lemak tubuh</string>
<string name="label_tdee">Total pengeluaran energi harian (TPEH)</string>
<string name="label_bmr">Tingkat metabolisme basal (TMB)</string>
<string name="label_bmi">Indeks massa tubuh (IMT)</string>
<string name="label_weight">Berat</string>
<string name="label_share_subject">ekspor data openScale (%s)</string>
<string name="label_share">Bagikan</string>
<string name="label_add_measurement">Tambah pengukuran</string>
<string name="label_add_user">Tambah pengguna</string>
<string name="label_empty">kosong</string>
<string name="label_delete">Hapus</string>
<string name="label_no">Tidak</string>
<string name="label_yes">Ya</string>
<string name="label_ok">OK</string>
<string name="label_cancel">Batal</string>
<string name="close_drawer">tutup</string>
<string name="open_drawer">buka</string>
<string name="action_donation">Donasi</string>
<string name="action_bluetooth_status">Status Bluetooth</string>
<string name="action_settings">Pengaturan</string>
<string name="title_general">Umum</string>
<string name="title_about">Tentang</string>
<string name="title_measurements">Pengukuran</string>
<string name="title_users">Pengguna</string>
<string name="title_statistics">Statistik</string>
<string name="title_table">Tabel</string>
<string name="title_graph">Bagan</string>
<string name="title_overview">Ringkasan</string>
<string name="label_daily">harian</string>
<string name="label_overwrite_backup">Timpa cadangan sebelumnya</string>
<string name="label_auto_backup_schedule">Jadwal cadangan</string>
<string name="label_auto_backup">Pencadangan otomatis</string>
<string name="label_estimate_measurement_summary">Berdasarkan berat badan, tinggi badan, usia, jenis kelamin, dll.</string>
<string name="label_estimated">Diperkirakan</string>
<string name="label_percent">Persen</string>
<string name="label_add_or_fix_translation">Tambahkan yang baru atau perbaiki yang sudah ada</string>
<string name="label_contribute_translation">Kontribusi terjemahan</string>
<string name="theme_light">Terang</string>
<string name="label_language">Bahasa</string>
<string name="label_monthly">bulanan</string>
<string name="label_calories">Kalori</string>
<string name="label_activity_level">Tingkat aktivitas</string>
<string name="label_measure_unit">Unit pengukuran</string>
<string name="amputation_level_leg">Kaki</string>
<string name="amputation_level_hand">Lengan</string>
<string name="info_data_all_deleted">Semua entri dihapus</string>
<string name="info_data_deleted">Entri dihapus</string>
<string name="error_goal_weight_required">Target berat harus diisi</string>
<string name="error_initial_weight_required">Berat awal harus diisi</string>
<string name="error_height_required">Tinggi harus diisi</string>
<string name="error_user_name_required">Nama harus diisi</string>
<string name="error_importing">Tidak dapat mengimpor</string>
<string name="error_exporting">Tidak dapat mengekspor</string>
<string name="error_value_range">Nilai di luar jangkauan</string>
<string name="error_value_required">Nilai harus diisi</string>
<string name="activity_level_heavy">Berat</string>
<string name="activity_level_extreme">Ekstrem</string>
<string name="label_slide_welcome_top_text">Selamat datang di
\nopenScale</string>
<string name="amputation_level_foot">Kaki</string>
<string name="amputation_level_none">Tidak ada amputasi</string>
<string name="label_slide_privacy_top_text">Lindungi data kesehatan pribadi Anda.</string>
<string name="label_slide_privacy_main_text">openScale tidak mengirim data apa pun ke cloud dan tidak memiliki izin untuk mengakses internet adalah jaminan.
\n
\nJika Anda benar-benar ingin menyinkronkan berat badan Anda ke GoogleFit atau dengan MQTT, Anda dapat menginstal <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync"> openScale sync</a>.</string>
<string name="label_slide_bluetooth_main_text">openScale memiliki dukungan bawaan untuk sejumlah timbangan Bluetooth dari berbagai produsen.
\n
\nKami terus meningkatkan dan memperluas rangkaian timbangan yang didukung. Anda dapat menemukan daftar lengkap dan tingkat dukungan untuk setiap skala di <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_amputation">Amputasi</string>
<string name="label_slide_opensource_main_text">openScale dilisensikan di bawah <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3 </a>.
\n
\nSemua perhitungan tubuh transparan dan tidak ada fungsi transfer data tersembunyi atau identifikasi pengguna yang dilakukan.
\n
\nAnda dapat menemukan kode sumber lengkap di <a href="https://github.com/oliexdev/openScale">GitHub</a>. Pelajari lebih lanjut tentang openScale dengan meninjaunya.</string>
<string name="label_slide_support_top_text">Bantu tingkatkan openScale.</string>
<string name="label_slide_support_main_text">OpenScale membutuhkan Anda. Buat <a href="https://github.com/oliexdev/openScale/issues">issue baru</a> jika Anda menemukan bug, memiliki ide, pertanyaan, atau ingin membantu mendukung skala Bluetooth Anda.
\n
\nDukung pencipta proyek ini dalam pengembangan lebih lanjut dan pemeliharaan berkelanjutan dengan memberikan openScale peringkat positif pada <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro"> GooglePlay </a> atau bintang itu di <a href="https://github.com/oliexdev/openScale"> GitHub </a>.
\n
\nDukungan dan umpan balik positif Anda sangat dihargai. Terima kasih.</string>
<string name="label_amputation_left">Amputasi kiri</string>
<string name="label_amputation_right">Amputasi kanan</string>
<string name="label_development">Pengembangan</string>
<string name="label_debug_log">Simpan log debug ke file</string>
<string name="label_select_user">Pilih pengguna</string>
<string name="label_configure_widget">Atur widget</string>
<string name="label_weekly">mingguan</string>
<string name="label_slide_welcome_main_text">Software open-source pelacak berat dan ukuran tubuh, dengan dukungan Bluetooth.</string>
<string name="label_slide_user_top_text">Mendukung multi-pengguna.</string>
<string name="label_slide_opensource_top_text">Ini adalah software open-source dengan tingkat transparansi yang tinggi.</string>
<string name="label_slide_bluetooth_top_text">Dukungan berbagai skala Bluetooth dari produsen yang berbeda.</string>
<string name="label_slide_metrics_top_text">Lacak dan analisis metrik tubuh Anda.</string>
<string name="label_slide_metrics_main_text">openScale mendukung lebih dari 22 metrik tubuh.
\n
\nAnda bisa mengaturnya sesuai kebutuhan.
\n
\nJika lemak tubuh, air tubuh dan massa tubuh tanpa lemak hilang, dapat diperkirakan berdasarkan <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations"> publikasi ilmiah </a>.</string>
<string name="info_create_new_user_on_scale">Buat pengguna baru pada skala .</string>
<string name="label_slide_user_main_text">Anda tidak perlu membuat akun online untuk menggunakan openScale.
\n
\nMetrik tubuh dihitung dari pengukuran.</string>
<string name="label_upgrade_to_openScale_pro">Silakan upgrade ke openScale pro untuk dukungan Bluetooth</string>
<string name="activity_level_mild">Ringan</string>
<string name="activity_level_moderate">Sedang</string>
</resources>

View File

@@ -19,27 +19,27 @@
<string name="label_delete">Elimina</string>
<string name="label_add_user">Aggiungi utente</string>
<string name="label_share">Condividi</string>
<string name="label_share_subject">Esportazione dati in formato CSV (%s)</string>
<string name="label_share_subject">Esportazione dei dati openScale (%s)</string>
<string name="label_weight">Peso</string>
<string name="label_bmi">Indice di massa corporea (IMC)</string>
<string name="label_bmi">Indice massa corporea (IMC)</string>
<string name="label_bmr">Metabolismo basale (MB)</string>
<string name="label_fat">Massa grassa</string>
<string name="label_water">Acqua corporea totale</string>
<string name="label_muscle">Muscoli</string>
<string name="label_lbm">Massa magra</string>
<string name="label_waist">Circonferenza della vita</string>
<string name="label_waist">Circonferenza vita</string>
<string name="label_hip">Circonferenza fianchi</string>
<string name="label_comment">Commento</string>
<string name="label_whtr">Rapporto vita-altezza</string>
<string name="label_whr">Rapporto vita-fianchi</string>
<string name="label_bone">Densità minerale ossea</string>
<string name="label_smartUserAssign">Assegnazione utente intelligente</string>
<string name="label_smartUserAssign">Assegnazione utente automatica</string>
<plurals name="label_days">
<item quantity="one">%d giorno</item>
<item quantity="other">%d giorni</item>
</plurals>
<string name="label_last_week">Media ultima settimana</string>
<string name="label_last_month">Media ultimo mese</string>
<string name="label_last_week">Media settimana precedente</string>
<string name="label_last_month">Media mese precedente</string>
<string name="label_weight_difference">Differenza di peso</string>
<string name="label_goal_date_is">Data dell\'obiettivo:</string>
<string name="label_days_left">Giorni rimanenti</string>
@@ -60,11 +60,11 @@
<string name="label_import">Importa</string>
<string name="label_export">Esporta</string>
<string name="label_delete_all">Cancella tutto</string>
<string name="error_value_required">Valore obbligatorio</string>
<string name="error_value_required">Valore richiesto</string>
<string name="error_value_range">Valore fuori dall\'intervallo</string>
<string name="error_exporting">Impossibile esportare</string>
<string name="error_importing">Impossibile importare</string>
<string name="error_user_name_required">Nome obbligatorio</string>
<string name="error_user_name_required">Nome richiesto</string>
<string name="error_height_required">Altezza necessaria</string>
<string name="error_initial_weight_required">Peso iniziale richiesto</string>
<string name="error_goal_weight_required">Obiettivo di peso richiesto</string>
@@ -79,30 +79,30 @@
<string name="info_is_enable">abilitato</string>
<string name="info_is_not_enable">disabilitato</string>
<string name="info_is_not_available">non disponibile</string>
<string name="info_bluetooth_try_connection">Connessione a:</string>
<string name="info_bluetooth_try_connection">Connettendo a:</string>
<string name="info_bluetooth_connection_lost">Connessione Bluetooth persa</string>
<string name="info_bluetooth_no_device">Nessun dispositivo Bluetooth rilevato</string>
<string name="info_bluetooth_no_device">Nessun dispositivo Bluetooth trovato</string>
<string name="info_bluetooth_no_device_set">Seleziona un dispositivo Bluetooth</string>
<string name="info_bluetooth_connection_successful">Connesso</string>
<string name="info_bluetooth_init">Inizializzazione dispositivo Bluetooth</string>
<string name="info_bluetooth_connection_error">Errore Bluetooth imprevisto</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] a %4$s aggiunto</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] aggiunto a %4$s</string>
<string name="info_new_data_duplicated">esistono già misurazioni con la stessa data e ora</string>
<string name="info_enter_user_name">Il tuo nome</string>
<string name="info_no_selected_user">Nessun utente. Si prega di creare uno nelle impostazioni.</string>
<string name="info_no_selected_user">Non esiste alcun utente. Si prega di crearne uno nelle impostazioni.</string>
<string name="info_no_evaluation_available">Impossibile valutare il valore</string>
<string name="question_really_delete">Eliminare la misurazione?</string>
<string name="question_really_delete_all">Eliminare tutte le misurazini di tutti gli utenti?</string>
<string name="question_really_delete_user">Elimina utente?</string>
<string name="question_really_delete_user">Eliminare utente\?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Connetti la bilancia all\'avvio</string>
<string name="label_mergeWithLastMeasurement">Unisci con l\'ultima misurazione</string>
<string name="label_bluetooth_searching">Ricerca della vostra bilancia Bluetooth</string>
<string name="label_bluetooth_searching">Ricerca della tua bilancia Bluetooth</string>
<string name="label_bluetooth_searching_finished">Ricerca terminata</string>
<string name="label_enable_labels">Etichetta dati</string>
<string name="label_enable_points">Punto dati</string>
<string name="label_delete_confirmation">Conferma eliminazione</string>
<string name="label_category_measurement_database">Banca dati delle misurazioni</string>
<string name="label_category_measurement_database">Database misurazioni</string>
<string name="label_maintainer">Responsabile</string>
<string name="label_website">Sito web</string>
<string name="label_license">Licenza</string>
@@ -111,16 +111,16 @@
<string name="label_reminder">Promemoria</string>
<string name="label_reminder_weekdays">Giorni</string>
<string name="label_reminder_time">Orario</string>
<string name="label_reminder_notify_text">Testo di notifica</string>
<string name="label_reminder_notify_text">Testo della notifica</string>
<string name="default_value_reminder_notify_text">È l\'ora di pesarsi</string>
<string name="info_on_date">il</string>
<string name="Monday">lunedì</string>
<string name="Tuesday">martedì</string>
<string name="Wednesday">mercoledì</string>
<string name="Thursday">giovedì</string>
<string name="Friday">venerdì</string>
<string name="Saturday">sabato</string>
<string name="Sunday">domenica</string>
<string name="Monday">Lunedì</string>
<string name="Tuesday">Martedì</string>
<string name="Wednesday">Mercoledì</string>
<string name="Thursday">Giovedì</string>
<string name="Friday">Venerdì</string>
<string name="Saturday">Sabato</string>
<string name="Sunday">Domenica</string>
<string name="label_bt_device_no_support">dispositivo non supportato</string>
<string name="label_exportBackup">Esporta backup</string>
<string name="label_importBackup">Importa backup</string>
@@ -139,9 +139,12 @@
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">No, grazie</string>
<string name="error_max_scale_users">È stato raggiunto il numero massimo di utenti simultanei per la bilancia</string>
<string name="info_step_on_scale_for_reference">Fare un passo a piedi nudi sulla bilancia per la misura di riferimento</string>
<string name="info_step_on_scale_for_reference">Posizionati sulla bilancia a piedi scalzi per le misurazioni di riferimento</string>
<string name="info_measuring">Misurazione del peso: %.2f</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Si è verificato un errore imprevisto.\n\nSi prega di creare un nuovo problema, inclusi i dettagli dell\'errore su https://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Si è verificato un errore imprevisto.
\n
\nSi prega di creare un nuovo problema, includendo i dettagli dell\'errore su
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Riavviare l\'app</string>
<string name="customactivityoncrash_error_activity_close_app">Chiudi l\'app</string>
<string name="customactivityoncrash_error_activity_error_details">Dettagli errore</string>
@@ -150,13 +153,13 @@
<string name="customactivityoncrash_error_activity_error_details_copy">Copia negli appunti</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Copiato negli appunti</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Info errore</string>
<string name="toggle_expand">Espandi/Chiudi tutti</string>
<string name="toggle_expand">Espandi/Chiudi</string>
<string name="edit">Modifica</string>
<string name="save">Salva</string>
<string name="label_month_view">Vista mensile</string>
<string name="label_weeks_view">Vista settimanale</string>
<string name="permission_not_granted">Autorizzazione non concessa</string>
<string name="permission_bluetooth_info">L\'autorizzazione di geolocalizzazione è necessaria per la ricerca dei dispositivi Bluetooth. Può essere revocata dopo aver trovato il dispositivo.</string>
<string name="permission_bluetooth_info">L\'autorizzazione alla geolocalizzazione è necessaria per la ricerca dei dispositivi Bluetooth. Può essere revocata dopo aver trovato il dispositivo.</string>
<string name="permission_bluetooth_info_title">Informazioni</string>
<string name="label_next">Avanti</string>
<string name="label_set_default_order">Imposta ordine predefinito</string>
@@ -168,14 +171,14 @@
<string name="label_language">Lingua</string>
<string name="theme_light">Chiaro</string>
<string name="theme_dark">Scuro</string>
<string name="label_contribute_translation">Contribuire alla traduzione</string>
<string name="label_contribute_translation">Contribuisci alla traduzione</string>
<string name="label_add_or_fix_translation">Aggiungi una nuova o modifica un\'esistente</string>
<string name="label_percent">Percentuale</string>
<string name="label_estimated">Stimato</string>
<string name="label_estimate_measurement_summary">Basato sul peso, l\'altezza, l\'età, il sesso, ecc.</string>
<string name="label_estimate_measurement_summary">Basato su peso, altezza, età, sesso, ecc.</string>
<string name="label_auto_backup">Backup automatico</string>
<string name="label_auto_backup_schedule">Pianificazione backup</string>
<string name="label_overwrite_backup">Sovrascrivere il backup precedente</string>
<string name="label_overwrite_backup">Sovrascrivere i backup precedenti</string>
<string name="label_daily">quotidiano</string>
<string name="label_weekly">settimanale</string>
<string name="label_monthly">mensile</string>
@@ -190,26 +193,26 @@
<string name="label_configure_widget">Configura widget</string>
<string name="label_visceral_fat">Grasso viscerale</string>
<string name="label_chest">Circonferenza torace</string>
<string name="label_thigh">Circonferenza della coscia</string>
<string name="label_thigh">Circonferenza coscia</string>
<string name="label_biceps">Circonferenza bicipiti</string>
<string name="label_neck">Circonferenza collo</string>
<string name="label_fat_caliper">Calibro del grasso corporeo</string>
<string name="label_caliper1_male">Plica cutanea del petto</string>
<string name="label_fat_caliper">Calibro grasso corporeo</string>
<string name="label_caliper1_male">Plica cutanea petto</string>
<string name="label_caliper2_male">Plica cutanea addominale</string>
<string name="label_caliper3_male">Plica cutanea della coscia</string>
<string name="label_caliper1_female">Plica cutanea del tricipite</string>
<string name="label_caliper3_male">Plica cutanea coscia</string>
<string name="label_caliper1_female">Plica cutanea tricipiti</string>
<string name="label_caliper2_female">Plica cutanea addominale</string>
<string name="label_caliper3_female">Plica cutanea del fianco</string>
<string name="label_add_measurement">Aggiungi la misurazione</string>
<string name="label_caliper3_female">Plica cutanea fianco</string>
<string name="label_add_measurement">Aggiungi misurazione</string>
<string name="label_tdee">Fabbisogno giornaliero</string>
<string name="action_donation">Donazione</string>
<string name="label_empty">vuoto</string>
<string name="trisa_scale_not_paired">Questa bilancia non è stata associata!
\n
\nTieni premuto il bottone che si trova sollo la bilancia per metterlà in modalità associazione e poi riconnetiti per ottenere la password del dispositivo.</string>
\nTieni premuto il bottone che si trova sotto la bilancia per metterla in modalità associazione e poi riconnetiti per ottenere la password del dispositivo.</string>
<string name="info_step_on_scale">Posizionati sulla bilancia a piedi scalzi</string>
<string name="info_bluetooth_no_device_retrying">Impossibile stabilire una connessione, nuovo tentativo in corso…</string>
<string name="info_bluetooth_connection_disconnected">Connessione Bluetooth chiusa</string>
<string name="info_bluetooth_connection_disconnected">Connessione Bluetooth terminata</string>
<string name="permission_location_service_info">Concedi l\'accesso alla posizione dalle impostazioni di Android per ricercare i dispositivi Bluetooth. Questa autorizzazione può essere poi eventualmente revocata.</string>
<string name="label_enable_legend">Legenda grafico</string>
<string name="label_enable_yaxis">Asse y</string>
@@ -223,19 +226,19 @@
\nRiconnettiti per ottenere i dati della misurazione.</string>
<string name="label_measurement_bar">Barra delle misurazioni</string>
<string name="permission_read_write_data_label">Leggi/Scrivi dati openScale</string>
<string name="permission_read_write_data_description">leggi/scrivi dati openScale, incluse le informazioni utente e tutte le misurazioni salvate</string>
<string name="permission_read_write_data_description">leggi/scrivi dati openScale, includendo le informazioni utente e tutte le misurazioni salvate</string>
<string name="activity_level_moderate">Moderato</string>
<string name="activity_level_mild">Medio</string>
<string name="activity_level_sedentary">Sefentario</string>
<string name="activity_level_sedentary">Sedentario</string>
<string name="label_activity_level">Livello di attività</string>
<string name="label_measure_unit">Unità di misura</string>
<string name="label_calories">Calorie</string>
<string name="activity_level_extreme">Estremo</string>
<string name="activity_level_heavy">Importante</string>
<string name="label_upgrade_to_openScale_pro">Passa alla versione pro se vuoi il protocollo Bluetooth</string>
<string name="label_upgrade_to_openScale_pro">Passa alla versione pro se vuoi utilizzare il protocollo Bluetooth</string>
<string name="label_slide_privacy_top_text">Proteggi i tuoi dati sanitari personali.</string>
<string name="label_slide_welcome_main_text">Software open-source per la rilevazione del peso e della metrica corporea, con supporto per la bilancia Bluetooth.</string>
<string name="label_slide_welcome_top_text">Benvenuti a
<string name="label_slide_welcome_main_text">Software open-source per la rilevazione del peso e delle metriche corporee, con supporto per la bilancia Bluetooth.</string>
<string name="label_slide_welcome_top_text">Benvenuti su
\nopenScale</string>
<string name="app_intro_back_button">Indietro</string>
<string name="app_intro_next_button">Avanti</string>
@@ -243,6 +246,53 @@
<string name="label_slide_support_top_text">Aiuta a migliorare openScale.</string>
<string name="label_slide_bluetooth_top_text">Sono supportate varie bilance Bluetooth di diversi produttori.</string>
<string name="label_slide_opensource_top_text">È un software open source con un alto grado di trasparenza.</string>
<string name="label_slide_user_top_text">Utenti multipli sono supportati.</string>
<string name="label_slide_user_top_text">Utenti multipli supportati.</string>
<string name="app_intro_done_button">Fatto</string>
<string name="label_slide_support_main_text">openScale ha bisogno di te. Crea una <a href="https://github.com/oliexdev/openScale/issues">nuova issue</a> se trovi un bug, se hai un\'idea, una domanda o se vuoi aiutarci a supportare la tua bilancia Bluetooth.
\n
\nSupporta il creatore di questo progetto in modo che questo possa essere aggiornato, scrivi una recensione positiva su <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> o aggiungi il progetto ai preferiti su <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nApprezziamo molto il tuo supporto e le recensioni positive. Grazie.</string>
<string name="label_slide_metrics_main_text">openScale supporta più di 22 metriche corporee.
\n
\nConfigurala per fare in modo che mostri quelle che ti interessano di più.
\n
\nSe la tua bilancia non supporta le metriche della quantità di acqua e della massa corporea magra, è possibile stimarli sulla base di <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">pubblicazioni scientifiche</a>.</string>
<string name="label_slide_metrics_top_text">Traccia e analizza le tue metriche corporee.</string>
<string name="label_slide_bluetooth_main_text">openScale supporta un gran numero di bilance Bluetooth di diversi produttori.
\n
\nMiglioriamo ed espandiamo il numero di bilance supportate costantemente. Puoi trovare la lista completa e il livello di supporto di ogni bilancia su <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_slide_opensource_main_text">openScale è distribuito con licenza <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
\n
\nTutte le misurazioni sono trasparenti e non avvengono trasferimenti di dati o di informazioni identificative nascosti.
\n
\nPuoi trovare il codice sorgente di questo programma su <a href="https://github.com/oliexdev/openScale">GitHub</a>. Se vuoi puoi controllare tu stesso.</string>
<string name="label_slide_user_main_text">openScale non richiede la creazione di account.
\n
\nI dati del corpo vengono calcolati dalle misurazioni effettuate.</string>
<string name="label_slide_privacy_main_text">openScale non invia dati in cloud e non richiede alcun permesso di accesso alla rete così puoi essere sicuro che ciò non avvenga.
\n
\nSe vuoi sincronizzare il tuo peso su GoogleFit o con MQTT puoi installare <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="amputation_level_leg">Gamba</string>
<string name="amputation_level_lower_leg_foot">Parte inferiore della gamba e piede</string>
<string name="amputation_level_foot">Piede</string>
<string name="amputation_level_arm">Braccio</string>
<string name="amputation_level_forearm_hand">Avambraccio e gamba</string>
<string name="amputation_level_hand">Mano</string>
<string name="amputation_level_none">Nessuna amputazione</string>
<string name="label_is_on_right_axis">Su asse destro</string>
<string name="label_trend">Tendenza</string>
<string name="label_prediction">Predizione</string>
<string name="label_trend_line">Tendenza</string>
<string name="info_assisted_weighing_choose_reference_user">Scegli un utente a cui sottrarre il peso</string>
<string name="info_assisted_weighing_no_reference_user">Nessun utente presente, creane uno</string>
<string name="info_assisted_weighing_old_reference_measurement">Attenzione: L\'ultima misurazione è più vecchia di un giorno</string>
<string name="info_assisted_weighing_no_reference_measurements">L\'utente non ha effettuato misurazioni</string>
<string name="label_amputation_right">Amputazione destra</string>
<string name="label_amputation_left">Amputazione sinistra</string>
<string name="label_amputation">Amputazione</string>
<string name="label_assisted_weighing">Pesata assistita</string>
<string name="info_enter_consent_code_for_scale_user">Inserire il NIP/codice di consenso per l\'utente della bilancia %s</string>
<string name="info_select_scale_user">Seleziona un utente</string>
<string name="info_create_new_user_on_scale">Crea un nuovo utente sulla bilancia.</string>
</resources>

View File

@@ -20,7 +20,7 @@
<string name="label_add_user">הוספת משתמש</string>
<string name="label_add_measurement">הוספת מדידה</string>
<string name="label_share">שיתוף</string>
<string name="label_share_subject">ייצוא נתונים ב־CSV (%s)</string>
<string name="label_share_subject">ייצוא נתונים כ־openScale (%s)</string>
<string name="label_weight">משקל</string>
<string name="label_bmi">מדד מסת גוף (BMI)</string>
<string name="label_bmr">קצב מטבולי בסיסי (BMR)</string>
@@ -285,10 +285,16 @@
<string name="amputation_level_none">אין קטיעה</string>
<string name="info_assisted_weighing_choose_reference_user">נא לבחור את המשתמש המופנה שממנו יוחסר המשקל</string>
<string name="info_assisted_weighing_no_reference_user">לא קיים משתמש מופנה, נא ליצור אחד</string>
<string name="info_assisted_weighing_old_reference_measurement">אזהרה: המדידה המופנית האחרונה בת יותר מיום</string>
<string name="info_assisted_weighing_old_reference_measurement">אזהרה: מדידת הייחוס האחרונה בת יותר מיום</string>
<string name="info_assisted_weighing_no_reference_measurements">למשתמש המופנה אין מדדים</string>
<string name="label_amputation_right">קטיעה ימנית</string>
<string name="label_amputation_left">קטיעה שמאלית</string>
<string name="label_amputation">קטיעה</string>
<string name="label_assisted_weighing">שקילה נתמכת</string>
<string name="label_trend">מגמה</string>
<string name="label_prediction">חיזוי</string>
<string name="label_trend_line">קו מגמה</string>
<string name="info_select_scale_user">בחירת משתמש במשקל</string>
<string name="info_enter_consent_code_for_scale_user">נא להקליד קוד אישי/הסכמה למשתמש במשקל %s</string>
<string name="info_create_new_user_on_scale">יצירת משתמש חדש במשקל.</string>
</resources>

View File

@@ -43,8 +43,8 @@
<string name="label_bluetooth_enable">体重計との起動時の接続</string>
<string name="label_last_month">先月の平均</string>
<string name="label_last_week">先週の平均</string>
<string name="error_exporting">エクスポートできませんでした</string>
<string name="error_importing">インポートできませんでした</string>
<string name="error_exporting">エクスポートできませんでした</string>
<string name="error_importing">インポートできませんでした</string>
<string name="error_height_required">身長が必要です</string>
<string name="error_user_name_required">名前が必要です</string>
<string name="info_data_all_deleted">全記録が削除されました</string>
@@ -71,13 +71,13 @@
<string name="info_no_evaluation_available">値を評価できません</string>
<string name="label_whtr">ウエストと身長の比</string>
<string name="label_whr">ウエストヒップ比</string>
<string name="action_bluetooth_status">Bluetoothステータス</string>
<string name="info_bluetooth_no_device">Bluetooth端末が見つかりませんでした</string>
<string name="action_bluetooth_status">Bluetooth ステータス</string>
<string name="info_bluetooth_no_device">Bluetooth 端末が見つかりませんでした</string>
<string name="info_bluetooth_try_connection">接続中:</string>
<string name="info_bluetooth_connection_error">予期しないBluetoothエラー</string>
<string name="info_bluetooth_connection_lost">Bluetooth接続が切断されました</string>
<string name="info_bluetooth_connection_error">予期しない Bluetooth エラー</string>
<string name="info_bluetooth_connection_lost">Bluetooth 接続が切断されました</string>
<string name="info_bluetooth_connection_successful">接続しました</string>
<string name="info_bluetooth_init">Bluetooth接続を初期化する</string>
<string name="info_bluetooth_init">Bluetooth 接続を初期化する</string>
<string name="default_value_reminder_notify_text">体重測定の時間</string>
<string name="label_reminder">リマインダー</string>
<string name="label_reminder_notify_text">通知内容</string>
@@ -107,9 +107,9 @@
<string name="label_bmr">基礎代謝率 (BMR)</string>
<string name="label_lbm">除脂肪体重</string>
<string name="label_bone">骨密度</string>
<string name="info_bluetooth_no_device_set">Bluetooth端末を選択してください</string>
<string name="info_bluetooth_no_device_set">Bluetooth 端末を選択してください</string>
<string name="info_new_data_duplicated">同じ日時のものがすでに存在している測定</string>
<string name="label_bluetooth_searching">Bluetooth対応体重計を検索中</string>
<string name="label_bluetooth_searching">Bluetooth 対応体重計を検索中</string>
<string name="label_bluetooth_searching_finished">検索終了</string>
<string name="label_maintainer">メンテナー</string>
<string name="label_website">ウェブサイト</string>
@@ -132,7 +132,7 @@
<string name="edit">編集</string>
<string name="save">保存</string>
<string name="permission_not_granted">権限が付与されていません</string>
<string name="permission_bluetooth_info">Bluetoothデバイスを検索するには位置情報のアクセス許可が必要です。デバイスが見つかった後に取り消すことができます。</string>
<string name="permission_bluetooth_info">Bluetooth デバイスを検索するには位置情報のアクセス許可が必要です。デバイスが見つかった後に取り消すことができます。</string>
<string name="language_default">システム標準</string>
<string name="label_language">言語</string>
<string name="theme_light">ライト</string>
@@ -147,8 +147,8 @@
<string name="label_automatic">自動</string>
<string name="label_exportBackup">バックアップをエクスポート</string>
<string name="label_importBackup">パックアップをインポート</string>
<string name="label_feedback_message_enjoying">openScaleに満足していただけましたか</string>
<string name="label_feedback_message_rate_app">Google PlayGithubで評価しませんか</string>
<string name="label_feedback_message_enjoying">openScale に満足していただけましたか?</string>
<string name="label_feedback_message_rate_app">Google PlayGithub で評価しませんか?</string>
<string name="label_feedback_message_issue">フィードバックを提供しますか?</string>
<string name="label_feedback_message_yes">はい</string>
<string name="label_feedback_message_no">あんまり</string>
@@ -174,10 +174,10 @@
<string name="info_measuring">測定体重: %.2f</string>
<string name="toggle_expand">展開の切り替え</string>
<string name="label_month_view">月表示</string>
<string name="label_share_subject">CSV データ エクスポート (%s)</string>
<string name="label_share_subject">CSV データエクスポート (%s)</string>
<string name="label_set_default_order">デフォルトの順序を設定</string>
<string name="label_weeks_view">週表示</string>
<string name="label_export_overwrite">以前のエクスポート \"%s\" を上書きしますか\?</string>
<string name="label_export_overwrite">以前のエクスポート \"%s\" を上書きしますか</string>
<string name="label_measurement_in_percent">% で測定</string>
<string name="label_estimate_measurement">推定測定</string>
<string name="label_estimation_formula">推定式</string>
@@ -187,7 +187,7 @@
<string name="label_daily">毎日</string>
<string name="label_weekly">毎週</string>
<string name="label_monthly">毎月</string>
<string name="label_your_bluetooth_scale">お使いのBluetooth体重計</string>
<string name="label_your_bluetooth_scale">お使いの Bluetooth 体重計</string>
<string name="label_long_press_drag_reorder">測定を長押し、ドラッグして並べ替え</string>
<string name="label_select_measurement">測定を選択</string>
<string name="label_select_user">ユーザーを選択</string>
@@ -217,12 +217,12 @@
<string name="trisa_scale_pairing_succeeded">ペアリングに成功しました!
\n
\n再接続して測定データを取得します。</string>
<string name="permission_read_write_data_description">ユーザー情報とすべての保存された測定値を含む、openScaleデータの読み取り/書き込み</string>
<string name="permission_read_write_data_label">openScaleデータの読み取り/書き込み</string>
<string name="label_tdee">1日の総エネルギー消費量 (TDEE)</string>
<string name="permission_read_write_data_description">ユーザー情報とすべての保存された測定値を含む、openScale データの読み取り/書き込み</string>
<string name="permission_read_write_data_label">openScale データの読み取り/書き込み</string>
<string name="label_tdee">1 日の総エネルギー消費量 (TDEE)</string>
<string name="label_calories">カロリー</string>
<string name="info_bluetooth_no_device_retrying">接続を確立できませんでした。再試行しています</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth接続が閉じられました</string>
<string name="info_bluetooth_no_device_retrying">接続を確立できませんでした。再接続中</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth 接続が閉じられました</string>
<string name="info_step_on_scale_for_reference">基準測定のために裸足で体重計に乗ってください</string>
<string name="permission_location_service_info">Bluetooth設定を検索するには、Android設定で位置情報アクセスを許可します。必要に応じて、後で取り消してください。</string>
<string name="label_enable_legend">グラフの凡例</string>
@@ -231,15 +231,15 @@
<string name="label_year_view">年表示</string>
<string name="label_measurement_bar">測定バー</string>
<string name="label_is_on_right_axis">右軸上に</string>
<string name="label_upgrade_to_openScale_pro">Bluetoothサポートは、openScale proにアップグレードしてください</string>
<string name="info_scale_low_battery">電池の残量が少なくなっています (%d%%)、体重計の電池を充電または交換してください</string>
<string name="label_upgrade_to_openScale_pro">Bluetooth に対応するには、openScale pro にアップグレードしてください</string>
<string name="info_scale_low_battery">電池の残量が少なくなっています (%d%%)、体重計の電池を充電または交換してください</string>
<string name="info_bluetooth_connection_error_scale_offline">体重計に接続できませんでした。オンになっていることを確認してください。</string>
<string name="label_age">年齢</string>
<string name="label_slide_welcome_main_text">オープンソースの体重・身体指標トラッカー、Bluetooth体重計をサポート</string>
<string name="label_slide_welcome_main_text">オープンソースの体重・身体指標トラッカー、Bluetooth 体重計に対応</string>
<string name="label_slide_privacy_top_text">個人の健康データを保護しよう。</string>
<string name="label_slide_privacy_main_text">openScale はどんなデータもクラウドに送信しません。インターネットのアクセス権限を持っていないことがこれを強く保証します。
\n
\n体重をGoogleFitまたはMQTTと本当に同期させたい場合は次のアプリがあります。 <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_privacy_main_text">openScale はどんなデータもクラウドに送信しません。インターネットのアクセス権限を持っていないことがこれを強く保証します。
\n
\n体重を GoogleFit または MQTT と実際に同期させたい場合は次のアプリがあります。 <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_user_top_text">複数ユーザをサポートしています。</string>
<string name="label_slide_user_main_text">openScale はオンラインアカウントの作成を必要としません。
\n
@@ -251,10 +251,21 @@
\n
\n全てのソースコードは <a href="https://github.com/oliexdev/openScale">GitHub</a>にあります。 納得できるまで openScale を検証してください。</string>
<string name="label_slide_bluetooth_top_text">様々なメーカーの いろんなBluetooth 体重計がサポートされています。</string>
<string name="label_slide_bluetooth_main_text">openScale は様々なメーカーの多くのBluetooth体重計との連携機能を内蔵しています。
\n
<string name="label_slide_bluetooth_main_text">openScale は様々なメーカーの多くの Bluetooth 体重計との連携機能を内蔵しています。
\n
\n我々は常にサポートする体重計のリストを改善し拡張しています。全てのリストと個々のサポート水準については <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a> で確認できます.</string>
<string name="label_slide_metrics_top_text">身体指標を追跡し分析する。</string>
<string name="label_slide_welcome_top_text">openScale
\nにようこそ</string>
<string name="app_intro_done_button">完了</string>
<string name="app_intro_back_button">Back</string>
<string name="app_intro_next_button">Next</string>
<string name="app_intro_skip_button">Skip</string>
<string name="amputation_level_leg"></string>
<string name="amputation_level_lower_leg_foot">下肢と足</string>
<string name="amputation_level_foot"></string>
<string name="amputation_level_arm"></string>
<string name="amputation_level_forearm_hand">前腕と脚</string>
<string name="amputation_level_hand"></string>
<string name="amputation_level_none">切断なし</string>
</resources>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources><string name="title_overview">개요</string>
<resources>
<string name="title_overview">개요</string>
<string name="title_graph">차트</string>
<string name="title_table"></string>
<string name="title_statistics">통계</string>
@@ -35,8 +36,8 @@
<string name="label_bone">골부피</string>
<string name="label_smartUserAssign">스마트 사용자 할당</string>
<plurals name="label_days">
<item quantity="other">%d 일</item>
</plurals>
<item quantity="other">%d 일</item>
</plurals>
<string name="label_last_week">지난 주 평균</string>
<string name="label_last_month">지난 달 평균</string>
<string name="label_weight_difference">무게 차이</string>
@@ -69,7 +70,7 @@
<string name="error_goal_weight_required">오류: 목표 무게 필요</string>
<string name="info_data_deleted">항목 삭제됨</string>
<string name="info_data_all_deleted">모든 항목 삭제됨</string>
<string name="info_data_exported">"다음 위치로 내보냄: "</string>
<string name="info_data_exported">다음 위치로 내보냄:</string>
<string name="info_data_imported">"다음 위치에서 가져옴: "</string>
<string name="info_enter_value_in">다음 위치에 있는 값</string>
<string name="info_enter_comment">비고</string>
@@ -148,7 +149,8 @@
\n측정치를 가져오기 위해서 다시 연결하세요.</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">예측하지 못한 오류 발생.
\n
\nhttps://github.com/oliexdev/openScale/issues 에 오류에 대한 자세한 정보를 넣어서 새 이슈를 만들어 주세요</string>
\n에 오류에 대한 자세한 정보를 넣어서 새 이슈를 만들어 주세요
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">앱 재시작</string>
<string name="customactivityoncrash_error_activity_close_app">앱 닫기</string>
<string name="customactivityoncrash_error_activity_error_details">오류 정보</string>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_overview">Apžvalga</string>
<string name="title_graph">Grafikas</string>
<string name="title_table">Lentelė</string>
<string name="title_statistics">Statistika</string>
<string name="title_users">Naudotojai</string>
<string name="title_measurements">Matavimai</string>
<string name="title_about">Apie</string>
<string name="action_settings">Nustatymai</string>
<string name="open_drawer">atidaryti</string>
<string name="label_yes">Taip</string>
<string name="label_no">Ne</string>
<string name="label_delete">Ištrinti</string>
<string name="label_empty">tušcia</string>
<string name="label_add_user">Pridėti naudotoją</string>
<string name="label_add_measurement">Pridėti matavimą</string>
<string name="label_share_subject">openScale duomenų eksportas (%s)</string>
<string name="label_bmi">Kūno masės indeksas</string>
<string name="label_bmr">Bazinė medžiagų apykaita (BMA)</string>
<string name="label_hip">Klubų apimtis</string>
<string name="label_waist">Juosmens apimtis</string>
<string name="label_whr">Juosmens/klubų santykis</string>
<string name="label_age">Amžius</string>
<string name="label_weight_difference">Svorio pokytis</string>
<string name="label_goal_date_is">Tikslo data:</string>
<string name="label_days_left">Liko dienų</string>
<string name="label_date">Data</string>
<string name="label_time">Laikas</string>
<string name="label_birthday">Gimimo diena</string>
<string name="label_height">Ūgis</string>
<string name="label_scale_unit">Vienetai</string>
<string name="label_gender">Lytis</string>
<string name="label_assisted_weighing">Svėrimas su pagalba</string>
<string name="label_amputation">Amputacija</string>
<string name="label_goal_weight">Siekamas svoris</string>
<string name="label_title_goal">Tikslas</string>
<string name="label_import">Importuoti</string>
<string name="label_export">Eksportuoti</string>
<string name="label_delete_all">Ištrinti viską</string>
<string name="error_value_required">Reikalinga</string>
<string name="action_donation">Paremti</string>
<string name="title_general">Bendra</string>
<string name="action_bluetooth_status">Bluetooth būsena</string>
<string name="close_drawer">uždaryti</string>
<string name="label_ok">OK</string>
<string name="label_cancel">Atšaukti</string>
<string name="label_share">Dalintis</string>
<string name="label_weight">Svoris</string>
<string name="label_muscle">Raumenys</string>
<string name="label_comment">Komentaras</string>
<string name="label_whtr">Juosmens/ūgio santykis</string>
<string name="label_bone">Kaulų masė</string>
<string name="label_user_name">Vardas</string>
<string name="label_male">Vyras</string>
<string name="label_amputation_left">Amputuota kairėje</string>
<string name="label_amputation_right">Amputuota dešinėje</string>
<string name="label_female">Moteris</string>
<string name="label_fat">Kūno riebalai</string>
<string name="label_goal_date">Tikslo data</string>
<string name="label_title_user">naudotojas</string>
<string name="label_title_last_measurement">paskutinis matavimas</string>
<string name="error_exporting">Eksportuoti nepavyko</string>
<string name="error_importing">Nepavyko importuoti</string>
<string name="error_user_name_required">Vardas privalomas</string>
<string name="error_height_required">Ūgis privalomas</string>
<string name="error_initial_weight_required">Pradinis svoris privalomas</string>
<string name="info_enter_value_in">Vertė</string>
<string name="info_enter_comment">Neprivalomas komentaras</string>
<string name="info_is_not_visible">yra nematomas</string>
<string name="info_is_enable">įjungta</string>
<string name="info_data_deleted">Įrašas ištrintas</string>
<string name="info_is_visible">matomas</string>
<string name="info_data_all_deleted">Visi įrašai ištrinti</string>
<string name="info_data_exported">Eksportuota į</string>
<string name="info_data_imported">Importuota iš</string>
<string name="info_is_not_enable">išjungta</string>
<string name="info_is_not_available">nėra</string>
<string name="label_last_week">Paskutinės savaitės vidurkis</string>
<string name="label_last_month">Paskutinio mėnesio vidurkis</string>
<string name="error_value_range">Neleistina vertė</string>
<string name="error_goal_weight_required">Svorio tikslas privalomas</string>
<string name="info_assisted_weighing_old_reference_measurement">Įspėjimas: paskutinis atraminis matavimas yra senesnis nei viena diena</string>
<string name="info_scale_low_battery">Baterija išsikrauna (%d%%), prašome įkrauti arba pakeisti baterijas</string>
<string name="info_bluetooth_connection_error">Netikėta Bluetooth klaida</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth ryšys išjungtas</string>
<string name="info_no_selected_user">Nėra nei vieno naudotojo. Prašome jį sukurti nustatymuose.</string>
<string name="question_really_delete">Ištrinti įrašą\?</string>
<string name="question_really_delete_user">Ištrinti vartotoją\?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Prisijungti prie svarstyklių paleidžiant programėlę</string>
<string name="label_mergeWithLastMeasurement">Sulieti su paskutiniu matavimu</string>
<string name="label_website">Svetainė</string>
<string name="label_license">Licencija</string>
<string name="label_reminder_weekdays">Dienos</string>
<string name="label_reminder_time">Laikas</string>
<string name="default_value_reminder_notify_text">Laikas pasisverti</string>
<string name="label_ignoreOutOfRange">Ignoruoti netinkamus duomenis</string>
<string name="label_initial_weight">Pradinis svoris</string>
<string name="label_prediction">Spėjimas</string>
<string name="label_trend">Tendencija</string>
<string name="label_help">Pagalba</string>
<string name="label_feedback_message_enjoying">Džiaugiatės openScale\?</string>
<string name="label_feedback_message_yes">Taip</string>
<string name="label_feedback_message_positive">OK</string>
<string name="edit">Redaguoti</string>
<string name="save">Išsaugoti</string>
<string name="label_measurement_bar">Matavimų juosta</string>
<string name="label_next">Toliau</string>
<string name="info_no_evaluation_available">Nepavyko įvertinti vertės</string>
<string name="info_bluetooth_connection_successful">Prisijungta</string>
<string name="info_assisted_weighing_no_reference_user">Nėra atraminio naudotojo, prašome jį sukurti</string>
<string name="label_category_measurement_database">Matavimų duomenų bazė</string>
<string name="info_bluetooth_try_connection">Prisijungiama prie:</string>
<string name="info_bluetooth_connection_lost">Bluetooth ryšys nutrūko</string>
<string name="info_bluetooth_no_device_retrying">Nepavyko užmegzti ryšio, bandoma iš naujo…</string>
<string name="info_bluetooth_no_device">Bluetooth įrenginys nerastas</string>
<string name="info_bluetooth_no_device_set">Pasirinkite Bluetooth įrenginį</string>
<string name="info_bluetooth_connection_error_scale_offline">Nepavyko prisijungti prie svarstyklių, patikrinkite ar jos įjungtos.</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] pridėta į %4$s</string>
<string name="info_new_data_duplicated">Matavimas su tokia pačia data ir laiku jau egzistuoja</string>
<string name="info_enter_user_name">Jūsų vardas</string>
<string name="question_really_delete_all">Ištrinti visus visų vartotojų įrašus\?</string>
<string name="label_enable_yaxis">Y ašis</string>
<string name="label_enable_labels">Duomenų žymė</string>
<string name="label_delete_confirmation">Ištrynimo patvirtinimas</string>
<string name="label_maintainer">Palaikytojas</string>
<string name="label_bluetooth_searching">Ieškoma Bluetooth svarstyklių</string>
<string name="label_bluetooth_searching_finished">Paieška baigta</string>
<string name="label_enable_legend">Grafiko legenda</string>
<string name="label_automatic">auto</string>
<string name="error_max_scale_users">Pasiektas didžiausias vienų svarstyklių naudotojų skaičius</string>
<string name="label_enable_points">Duomenų taškas</string>
<string name="label_theme">Tema</string>
<string name="info_step_on_scale_for_reference">Prašome basomis kojomis užlipti ant svarstyklių atraminiam matavimui</string>
<string name="info_step_on_scale">Prašome basomis užlipti ant svarstyklių</string>
<string name="label_reminder">Priminimas</string>
<string name="Tuesday">Antradienis</string>
<string name="Wednesday">Trečiadienis</string>
<string name="Thursday">Ketvirtadienis</string>
<string name="label_feedback_message_rate_app">Kaip dėl reitingo Google Play ar Github\?</string>
<string name="Monday">Pirmadienis</string>
<string name="info_on_date">" "</string>
<string name="label_exportBackup">Eksportuoti atsarginę kopiją</string>
<string name="Friday">Penktadienis</string>
<string name="Saturday">Šeštadienis</string>
<string name="Sunday">Sekmadienis</string>
<string name="label_not_found">nerasta</string>
<string name="label_bt_device_no_support">įrenginys nepalaikomas</string>
<string name="label_importBackup">Importuoti atsarginę kopiją</string>
<string name="label_backup">Atsarginė kopija</string>
<string name="label_export_dir">Eksporto katalogas</string>
<string name="customactivityoncrash_error_activity_error_details_title">Klaidos informacija</string>
<string name="label_feedback_message_issue">Ar sutiktumėte suteikti grįžtamąjį ryšį\?</string>
<string name="label_feedback_message_no">Nelabai</string>
<string name="label_feedback_message_negative">Ne, ačiū</string>
<string name="info_select_scale_user">Pasirinkite svarstyklių naudotoją</string>
<string name="info_measuring">Pamatuotas svoris: %.2f</string>
<string name="customactivityoncrash_error_activity_restart_app">Perkrauti programėlę</string>
<string name="customactivityoncrash_error_activity_error_details">Klaidos informacija</string>
<string name="permission_not_granted">Leidimas nesuteiktas</string>
<string name="permission_bluetooth_info_title">Info</string>
<string name="customactivityoncrash_error_activity_close_app">Uždaryti programėlę</string>
<string name="customactivityoncrash_error_activity_error_details_close">Uždaryti</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Nukopijuota į iškarpinę</string>
<string name="label_day_view">Dienos vaizdas</string>
<string name="label_month_view">Mėnesio vaizdas</string>
<string name="label_weeks_view">Savaitės vaizdas</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Kopijuoti į iškarpinę</string>
<string name="label_year_view">Metų vaizdas</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Klaidos informacija</string>
</resources>

View File

@@ -19,7 +19,7 @@
<string name="label_delete">Slett</string>
<string name="label_add_user">Legg til bruker</string>
<string name="label_share">Del</string>
<string name="label_share_subject">CVS-dataeksport (%s)</string>
<string name="label_share_subject">openScale data-eksport (%s)</string>
<string name="label_weight">Vekt</string>
<string name="label_bmi">Kroppsmasseindeks (KMI)</string>
<string name="label_bmr">Hvilestoffskifte (BMR)</string>
@@ -36,8 +36,8 @@
<string name="label_whr">Midje-til-hofte -forhold</string>
<string name="label_smartUserAssign">Smart brukertildeling</string>
<plurals name="label_days">
<item quantity="one">dag %d</item>
<item quantity="other">dager %d</item>
<item quantity="one">%d dag</item>
<item quantity="other">%d dager</item>
</plurals>
<string name="label_last_week">Gjennomsnitt siste uke</string>
<string name="label_last_month">Gjennomsnitt den siste måneden</string>
@@ -87,7 +87,7 @@
<string name="info_bluetooth_connection_successful">Tilkoblet</string>
<string name="info_bluetooth_init">Igangsett Blåtannsenhet</string>
<string name="info_bluetooth_connection_error">Uventet Blåtannsfeil</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] til %4$s lagt til</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] lagt til til %4$s</string>
<string name="info_new_data_duplicated">måling med samme dato og tid finnes allerede</string>
<string name="info_enter_user_name">Ditt navn</string>
<string name="info_no_selected_user">Ingen bruker finnes. Opprett en i innstillingene.</string>
@@ -275,7 +275,7 @@
\nDu kan finne hele kildekoden på <a href="https://github.com/oliexdev/openScale">GitHub</a>. Gjør deg trygg på openScale ved å gå igjennom koden.</string>
<string name="label_slide_welcome_main_text">Fri programvare for vekt- og kroppsmålingssporing, med støtte for Blåtannsvekter.</string>
<string name="amputation_level_lower_leg_foot">Legg og fotblad</string>
<string name="info_assisted_weighing_old_reference_measurement">Siste referansemåling er over én dag gammel</string>
<string name="info_assisted_weighing_old_reference_measurement">Advarsel: Siste referansemåling er over én dag gammel</string>
<string name="amputation_level_leg">Legg</string>
<string name="amputation_level_foot">Fot</string>
<string name="amputation_level_arm">Arm</string>
@@ -289,4 +289,10 @@
<string name="label_amputation_left">Amputasjon venstre</string>
<string name="label_amputation">Amputasjon</string>
<string name="label_assisted_weighing">Assistert veiing</string>
<string name="label_trend_line">Tendenslinje</string>
<string name="label_trend">Tendens</string>
<string name="label_prediction">Prognose</string>
<string name="info_select_scale_user">Velg vektbruker</string>
<string name="info_enter_consent_code_for_scale_user">Skriv inn PIN/samtykkekode for vektbruker %s</string>
<string name="info_create_new_user_on_scale">Lag ny vektbruker.</string>
</resources>

View File

@@ -20,7 +20,7 @@
<string name="label_add_user">Gebruiker toevoegen</string>
<string name="label_add_measurement">Meting toevoegen</string>
<string name="label_share">Delen</string>
<string name="label_share_subject">CSV-gegevens exporteren (%s)</string>
<string name="label_share_subject">openScale-gegevens exporteren (%s)</string>
<string name="label_weight">Gewicht</string>
<string name="label_bmi">Body Mass Index (BMI)</string>
<string name="label_bmr">Basaal Metabolisme (BMR)</string>
@@ -241,8 +241,8 @@
\nopenScale</string>
<string name="label_slide_welcome_main_text">Open source gewichts- en lichaamskenmerkenlogboek, met ondersteuning voor Bluetooth-weegschalen.</string>
<string name="label_slide_privacy_top_text">Bescherm je persoonlijke gezondheidsgegevens.</string>
<string name="label_slide_privacy_main_text">openScale verstuurt geen gegevens naar de cloud. Het ontbreken van toestemming voor internettoegang is daar het beste bewijs van.
\n
<string name="label_slide_privacy_main_text">openScale verstuurt geen gegevens naar de cloud. Het ontbreken van toestemming voor internettoegang is daar het beste bewijs van.
\n
\nAls je je gewicht wilt synchroniseren met Google Fit of MQTT, dan kun je <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale-sync</a> installeren.</string>
<string name="label_slide_user_top_text">De app ondersteunt meerdere gebruikers.</string>
<string name="label_slide_user_main_text">openScale vereist geen online-account.
@@ -251,11 +251,11 @@
<string name="label_slide_opensource_top_text">De app is open source en kent een hoge mate van transparantie.</string>
<string name="label_slide_bluetooth_top_text">De app ondersteunt verscheidene Bluetooth-weegschalen van verschillende fabrikanten.</string>
<string name="label_slide_metrics_top_text">Verzamel en analyseer je lichaamskenmerken.</string>
<string name="label_slide_opensource_main_text">openScale wordt verspreid onder de <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>-licentie.
\n
\nAlle lichaamsberekeningen zijn transparant. Er vindt geen verborgen gegevensoverdracht of gebruikersidentificatie plaats.
\n
\nJe kunt de volledige broncode bekijken op <a href="https://github.com/oliexdev/openScale">GitHub</a>. Probeer openScale vandaag nog!</string>
<string name="label_slide_opensource_main_text">openScale heeft een licentie onder de <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE"> GPLv3 </a>.
\n
\nAlle lichaamsberekeningen zijn transparant en er wordt geen verborgen gegevensoverdracht of gebruikersidentificatie uitgevoerd.
\n
\nU kunt de volledige broncode vinden op <a href="https://github.com/oliexdev/openScale"> GitHub </a>. Overtuig uzelf van openScale door het uit te proberen.</string>
<string name="label_slide_bluetooth_main_text">openScale heeft ingebouwde ondersteuning voor een aantal Bluetooth-weegschalen van verschillende fabrikanten.
\n
\nDe ondersteuning van huidige en nieuwe weegschalen wordt continue verbeterd. De volledige lijst met weegschalen en hun ondersteuningsniveaus is te vinden op <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
@@ -289,4 +289,10 @@
<string name="label_amputation_left">Amputatie (links)</string>
<string name="label_amputation">Amputatie</string>
<string name="label_assisted_weighing">Begeleid wegen</string>
<string name="label_trend">Trend</string>
<string name="label_prediction">Voorspelling</string>
<string name="label_trend_line">Trendlijn</string>
<string name="info_create_new_user_on_scale">Maak een nieuwe gebruiker aan.</string>
<string name="info_select_scale_user">Kies een gebruiker</string>
<string name="info_enter_consent_code_for_scale_user">Voer de pin- of toegangscode in van %s</string>
</resources>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_overview">Przegląd</string>
<string name="title_graph">Wykresy</string>
<string name="title_table">Tabele</string>
<string name="title_overview">Podsumowanie</string>
<string name="title_graph">Wykres</string>
<string name="title_table">Tabela</string>
<string name="title_statistics">Statystyki</string>
<string name="title_users">Użytkownicy</string>
<string name="title_measurements">Pomiary</string>
@@ -19,7 +19,7 @@
<string name="label_delete">Usuń</string>
<string name="label_add_user">Dodaj użytkownika</string>
<string name="label_share">Udostępnij</string>
<string name="label_share_subject">Export danych do CSV (%s)</string>
<string name="label_share_subject">openScale eksport danych (%s)</string>
<string name="label_weight">Waga</string>
<string name="label_bmi">Wskaźnik masy ciała (BMI)</string>
<string name="label_bmr">Wskaźnik podstawowej przemiany materii (BMR)</string>
@@ -47,10 +47,10 @@
<string name="label_date">Data</string>
<string name="label_time">Czas</string>
<string name="label_birthday">Urodziny</string>
<string name="label_user_name">Imie</string>
<string name="label_user_name">Imię</string>
<string name="label_height">Wzrost</string>
<string name="label_scale_unit">Jednostka wagi</string>
<string name="label_gender">ęć</string>
<string name="label_gender">eć</string>
<string name="label_male">Mężczyzna</string>
<string name="label_female">Kobieta</string>
<string name="label_goal_weight">Docelowa waga</string>
@@ -59,7 +59,7 @@
<string name="label_title_last_measurement">ostatni pomiar</string>
<string name="label_title_goal">Cel</string>
<string name="label_import">Import</string>
<string name="label_export">Export</string>
<string name="label_export">Eksport</string>
<string name="label_delete_all">Usuń wszystko</string>
<string name="error_value_required">Wymagana wartość</string>
<string name="error_value_range">Wartość poza zakresem</string>
@@ -102,7 +102,7 @@
<string name="label_bluetooth_searching_finished">Wyszukiwanie zakończone</string>
<string name="label_enable_labels">Etykiety danych</string>
<string name="label_enable_points">Punkt danych</string>
<string name="label_delete_confirmation">Potwierdzenie usnięcia</string>
<string name="label_delete_confirmation">Potwierdzenie usunięcia</string>
<string name="label_category_measurement_database">Baza pomiarów</string>
<string name="label_maintainer">Opiekun</string>
<string name="label_website">Strona internetowa</string>
@@ -123,7 +123,7 @@
<string name="Saturday">Sobota</string>
<string name="Sunday">Niedziela</string>
<string name="label_bt_device_no_support">urządzenie nie jest wspierane</string>
<string name="label_exportBackup">Exportuj kopie zapasową</string>
<string name="label_exportBackup">Eksportuj kopie zapasową</string>
<string name="label_importBackup">Importuj kopie zapasową</string>
<string name="label_backup">Kopia zapasowa</string>
<string name="label_export_dir">Katalog do eksportu</string>
@@ -138,7 +138,7 @@
<string name="label_feedback_message_yes">Tak</string>
<string name="label_feedback_message_no">Nie bardzo</string>
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">No, dzięki</string>
<string name="label_feedback_message_negative">Nie, dzięki</string>
<string name="error_max_scale_users">Osiągnięto maksymalną ilość użytkowników wagi</string>
<string name="info_step_on_scale_for_reference">Proszę stanąć boso na wadze w celu przeprowadzenia pomiaru referencyjnego</string>
<string name="info_measuring">Mierzona waga: %.2f</string>
@@ -203,7 +203,7 @@
<string name="label_caliper3_female">Fałd skórny biodra</string>
<string name="label_add_measurement">Dodaj pomiary</string>
<string name="label_tdee">Całkowity dzienny wydatek energii (TDEE)</string>
<string name="info_step_on_scale">Proszę stanąć boso na wagi</string>
<string name="info_step_on_scale">Proszę stanąć boso na wadze</string>
<string name="info_bluetooth_no_device_retrying">Nie można nawiązać połączenia, ponawianie…</string>
<string name="info_bluetooth_connection_disconnected">Zakończono połączenie bluetooth</string>
<string name="label_enable_legend">Legenda</string>
@@ -224,7 +224,7 @@
<string name="activity_level_heavy">Intensywny</string>
<string name="activity_level_extreme">Ekstremalny</string>
<string name="label_contribute_translation">Pomoc w tłumaczeniu</string>
<string name="label_add_or_fix_translation">Dodaj nowy lub napraw istniejący</string>
<string name="label_add_or_fix_translation">Dodaj nowe lub popraw istniejące</string>
<string name="label_development">Rozwój</string>
<string name="activity_level_sedentary">Siedzący</string>
<string name="permission_read_write_data_description">odczyt/zapis danych openScale, w tym informacji o użytkowniku i wszystkich zapisanych pomiarach</string>
@@ -245,7 +245,7 @@
<string name="label_slide_privacy_main_text">openScale nie wysyła żadnych danych do chmury, a brak zgody na dostęp do Internetu jest tego silną gwarancją.
\n
\nJeśli naprawdę chcesz zsynchronizować swoją wagę z GoogleFit lub MQTT, możesz zainstalować <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_user_top_text">Podtrzymuje wielu użytkowników.</string>
<string name="label_slide_user_top_text">Obsługuje wielu użytkowników.</string>
<string name="label_slide_user_main_text">openScale nie wymaga utworzenia konta online.
\n
\nWskaźniki ciała są obliczane na podstawie pomiarów.</string>
@@ -266,13 +266,34 @@
\n
\nJeśli brakuje tkanki tłuszczowej, wody w organizmie i beztłuszczowej masy ciała, można to oszacować na podstawie <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">publikacji naukowych</a>.</string>
<string name="label_slide_support_top_text">Pomóż ulepszyć openScale.</string>
<string name="label_slide_support_main_text">openScale cię potrzebuje. Zgłos <a href="https://github.com/oliexdev/openScale/issues">nowy problem</a>, jeśli znajdziesz błąd, masz pomysł, pytanie lub chcesz dodać swoją wagę Bluetooth.
\n
<string name="label_slide_support_main_text">openScale cię potrzebuje. Zgłoś <a href="https://github.com/oliexdev/openScale/issues">nowy problem</a>, jeśli znajdziesz błąd, masz pomysł, pytanie lub chcesz dodać swoją wagę Bluetooth.
\n
\nMotywuj twórcę tego projektu do dalszego rozwoju i ciągłej konserwacji, przyznając openScale pozytywną ocenę w <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">Google Play</a> lub gwiazdkę w <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nYour support and positive feedback is highly appreciated. Thank you.</string>
<string name="app_intro_skip_button">Pomin</string>
<string name="app_intro_skip_button">Pomiń</string>
<string name="app_intro_next_button">Kolejny</string>
<string name="app_intro_back_button">Nazad</string>
<string name="app_intro_back_button">Powrót</string>
<string name="app_intro_done_button">Gotowe</string>
<string name="info_assisted_weighing_old_reference_measurement">Uwaga: poprzedni pomiar jest starszy niż dzień</string>
<string name="amputation_level_lower_leg_foot">Dolna część nogi i stopa</string>
<string name="label_amputation_right">Amputacje prawych kończyn</string>
<string name="label_amputation_left">Amputacje lewych kończyn</string>
<string name="label_amputation">Amputacje</string>
<string name="amputation_level_hand">Nadgarstek</string>
<string name="label_assisted_weighing">Ważenie z poradami</string>
<string name="amputation_level_leg">Noga</string>
<string name="amputation_level_foot">Stopa</string>
<string name="amputation_level_arm">Ręka</string>
<string name="amputation_level_forearm_hand">Przedramię i noga</string>
<string name="amputation_level_none">Bez amputacji</string>
<string name="info_assisted_weighing_choose_reference_user">Wybierz wzorowego użytkownika, waga jakiego zostanie odejmowana</string>
<string name="info_assisted_weighing_no_reference_user">Brak dostępnego użytkownika wzorcowego, należy go utworzyć</string>
<string name="info_assisted_weighing_no_reference_measurements">Użytkownik wzorcowy nie ma żadnych pomiarów</string>
<string name="label_trend">Trend</string>
<string name="label_trend_line">Linia trendu</string>
<string name="label_prediction">Prognoza</string>
<string name="info_enter_consent_code_for_scale_user">Wprowadź kod PIN/kod zgody dla użytkownika wagi %s</string>
<string name="info_create_new_user_on_scale">Utwórz nowego użytkownika wagi.</string>
<string name="info_select_scale_user">Wybierz użytkownika wagi</string>
</resources>

View File

@@ -10,25 +10,25 @@
<string name="action_bluetooth_status">Estado do Bluetooth</string>
<string name="action_settings">Configurações</string>
<string name="default_value_reminder_notify_text">Hora de pesar-se</string>
<string name="error_height_required">Erro: falta a altura</string>
<string name="error_height_required">Altura requerida</string>
<string name="error_exporting">Erro ao exportar</string>
<string name="error_goal_weight_required">Erro: peso desejado é obrigatório</string>
<string name="error_goal_weight_required">Meta de peso requerida</string>
<string name="error_importing">Erro ao importar</string>
<string name="error_initial_weight_required">Erro: falta o peso</string>
<string name="error_initial_weight_required">Peso inicial requerido</string>
<string name="error_max_scale_users">Quantidade máxima de utilizadores concurrentes atingidos</string>
<string name="error_user_name_required">Erro: Nome do utilizador é obrigatório</string>
<string name="error_value_range">Valor não está no intervalo</string>
<string name="error_user_name_required">Nome requerido</string>
<string name="error_value_range">Valor fora do intervalo</string>
<string name="error_value_required">Valor é obrigatório</string>
<string name="info_bluetooth_connection_error">Ocorreu um erro inesperado com o Bluetooth</string>
<string name="info_bluetooth_connection_lost">Ligação bluetooth perdida</string>
<string name="info_bluetooth_connection_successful">Ligação estabelecida</string>
<string name="info_bluetooth_connection_lost">Conexão Bluetooth perdida</string>
<string name="info_bluetooth_connection_successful">Conectado</string>
<string name="info_bluetooth_init">Iniciar aparelho Bluetooh</string>
<string name="info_bluetooth_no_device">Nenhum aparelho Bluetooth encontrado</string>
<string name="info_bluetooth_try_connection">Conectando a:</string>
<string name="info_data_all_deleted">Todos os dados foram apagados</string>
<string name="info_data_deleted">Dados apagados</string>
<string name="info_data_exported">Dados exportados para</string>
<string name="info_data_imported">Dados importados de</string>
<string name="info_data_exported">Exportado para</string>
<string name="info_data_imported">Importado de</string>
<string name="info_enter_comment">Digite um comentário opcional</string>
<string name="info_enter_user_name">Digite seu nome</string>
<string name="info_enter_value_in">Digite o valor em</string>
@@ -53,8 +53,8 @@
<string name="label_comment">Comentário</string>
<string name="label_date">Data</string>
<plurals name="label_days">
<item quantity="one">"dia %d "</item>
<item quantity="other">"dias %d "</item>
<item quantity="one">dia %d</item>
<item quantity="other">dias %d</item>
</plurals>
<string name="label_days_left">Dias restantes</string>
<string name="label_delete">Apagar</string>
@@ -95,7 +95,7 @@
<string name="label_title_user">utilizador</string>
<string name="label_user_name">Nome</string>
<string name="label_waist">Perimetro abdominal</string>
<string name="label_water">Quantidade de água</string>
<string name="label_water">Água corporal total</string>
<string name="label_weight">Peso</string>
<string name="label_weight_difference">Diferença de peso</string>
<string name="label_whr">Relação anca-cintura</string>
@@ -120,7 +120,7 @@
<string name="label_bmr">Taxa Metabólica Basal (TMB)</string>
<string name="label_lbm">Massa Magra</string>
<string name="label_bone">Massa óssea</string>
<string name="info_bluetooth_no_device_set">Sem dispositivo Bluetooth selecionado</string>
<string name="info_bluetooth_no_device_set">Selecione um dispositivo Bluetooth</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] para %4$s adicionado</string>
<string name="info_new_data_duplicated">media já inserida com a mesma data e hora</string>
<string name="label_mergeWithLastMeasurement">Juntar à última medida</string>
@@ -136,13 +136,15 @@
<string name="label_help">Ajuda</string>
<string name="label_feedback_message_enjoying">Gostou do openScale?</string>
<string name="label_feedback_message_rate_app">Que tal avaliar o openScale no GooglePlay ou no GitHub\?</string>
<string name="label_feedback_message_issue">Qual é a sua opinião sobre a app?</string>
<string name="label_feedback_message_issue">Qual é a sua opinião sobre o app\?</string>
<string name="label_feedback_message_yes">Sim</string>
<string name="label_feedback_message_no">Não necessáriamente</string>
<string name="label_feedback_message_positive">Ok</string>
<string name="label_feedback_message_negative">Não, obrigado</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Um erro inesperado ocorreu.
\nPor favor crie um novo issue incluindo os detalhes no https://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Um erro inesperado ocorreu.
\n
\nFavor criar um novo issue incluindo detalhes do erro em
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Recomeçar a app</string>
<string name="customactivityoncrash_error_activity_close_app">Fecha a app</string>
<string name="customactivityoncrash_error_activity_error_details">Detalhes do erro</string>
@@ -157,11 +159,11 @@
<string name="label_month_view">Visão mensal</string>
<string name="label_weeks_view">Visão semanal</string>
<string name="permission_not_granted">Não permitido</string>
<string name="permission_bluetooth_info">openScale precisa de permissão para procurar dispositivos Bluetooth</string>
<string name="permission_bluetooth_info">Permissão de Localização necessária para buscar dispositivos Bluetooth. Pode ser revogada após encontrar dispositivo.</string>
<string name="permission_bluetooth_info_title">Infos</string>
<string name="label_next">Próximo</string>
<string name="label_set_default_order">Define ordem por defeito</string>
<string name="label_export_overwrite">"Gravar sobre o export \"%s\" anterior ?"</string>
<string name="label_export_overwrite">Gravar sobre o export \"%s\" anterior \?</string>
<string name="label_measurement_in_percent">Medida em %</string>
<string name="label_estimate_measurement">Medida estimada</string>
<string name="label_estimation_formula">Formula de estimativa</string>
@@ -181,15 +183,34 @@
<string name="info_step_on_scale">Por favor suba descalço para a balança</string>
<string name="label_enable_legend">Legenda</string>
<string name="label_enable_yaxis">Eixo Y</string>
<string name="trisa_scale_not_paired">A balança não foi pareada!
<string name="trisa_scale_not_paired">A balança não foi pareada!
\n
\nPressione o botão da balança para mudar para o modo de pareamento e então reconecte para recuperar a palavra-passe do aparelho.</string>
<string name="trisa_scale_pairing_succeeded">Pareamento feito!
\n
\nReconecte para recuperar os dados das medições.</string>
<string name="label_day_view">Visualização diária</string>
<string name="label_year_view">Visualização anual</string>
<string name="label_long_press_drag_reorder">Toque firmemente e arraste a medição para reorganiza-la</string>
<string name="permission_read_write_data_description">leia/escreva dados no openScale, incluindo informações dos utilizadores e todas as medidas salvas.</string>
<string name="permission_read_write_data_description">leia/escreva dados no openScale, incluindo informações dos utilizadores e todas as medidas salvas</string>
<string name="label_measurement_bar">Barra de medidas</string>
<string name="action_donation">Doação</string>
<string name="label_empty">Vazio</string>
<string name="label_trend">Tendência</string>
<string name="label_trend_line">Linha de tendência</string>
<string name="label_prediction">Previsão</string>
<string name="info_bluetooth_connection_error_scale_offline">Sem conexão com balança, certifique-se que está ligada.</string>
<string name="info_scale_low_battery">Bateria baixa (%d%%), favor recarregar ou trocar as baterias da balança</string>
<string name="info_assisted_weighing_choose_reference_user">Escolha o usuário-referência cujo peso será descontado</string>
<string name="info_assisted_weighing_no_reference_user">Sem usuário-referência disponível, favor criar um</string>
<string name="info_assisted_weighing_old_reference_measurement">Alerta: última medida de referência tem mais de um dia</string>
<string name="info_assisted_weighing_no_reference_measurements">Usuário-referência sem medidas</string>
<string name="label_amputation_right">Amputação direita</string>
<string name="label_amputation_left">Amputação esquerda</string>
<string name="label_amputation">Amputação</string>
<string name="label_assisted_weighing">Pesagem assistida</string>
<string name="label_age">Idade</string>
<string name="info_select_scale_user">Selecione a balança para utilização</string>
<string name="info_enter_consent_code_for_scale_user">Entre com PIN/Código de Consentimento do usuário %s</string>
<string name="permission_read_write_data_label">Ler/Escrever dados do openScale</string>
</resources>

View File

@@ -10,11 +10,13 @@
<string name="label_enable_legend">Legenda</string>
<string name="info_step_on_scale_for_reference">Por favor, suba para a balança para obter medidas de referência</string>
<string name="info_bluetooth_connection_disconnected">Conexão Bluetooth terminada</string>
<string name="permission_read_write_data_description">leia/escreva dados no openScale, incluindo informações dos utilizadores e todas as medidas salvas.</string>
<string name="trisa_scale_pairing_succeeded">Pareamento feito!
<string name="permission_read_write_data_description">leia/escreva dados no openScale, incluindo informações dos utilizadores e todas as medidas salvas</string>
<string name="trisa_scale_pairing_succeeded">Emparelhamento concluído!
\n
\nReconecte para recuperar os dados das medições.</string>
<string name="trisa_scale_not_paired">A balança não foi pareada!
\nPressione o botão da balança para mudar para o modo de pareamento e então reconecte para recuperar a palavra-passe do aparelho.</string>
<string name="trisa_scale_not_paired">Esta balança não foi emparelhada!
\n
\nPressione o botão da balança para mudar para o modo de emparelhamento e então reconecte para recuperar a palavra-passe do aparelho.</string>
<string name="label_add_measurement">Adicionar medição</string>
<string name="info_enter_value_in">Digite o valor em</string>
<string name="label_long_press_drag_reorder">Toque firmemente e arraste a medição para reorganiza-la</string>
@@ -30,7 +32,7 @@
<string name="label_estimation_formula">Formula de estimativa</string>
<string name="label_estimate_measurement">Medida estimada</string>
<string name="label_measurement_in_percent">Medida em %</string>
<string name="label_export_overwrite">"Gravar sobre o export \"%s\" anterior \?"</string>
<string name="label_export_overwrite">Gravar sobre o export anterior \"%s\"\?</string>
<string name="label_set_default_order">Define ordem por defeito</string>
<string name="label_next">Próximo</string>
<string name="permission_bluetooth_info_title">Infos</string>
@@ -99,7 +101,7 @@
<string name="info_no_selected_user">Nenhum utilizador existe. Por favor, crie um novo utilizador em Configurações.</string>
<string name="info_enter_user_name">Digite seu nome</string>
<string name="info_new_data_duplicated">media já inserida com a mesma data e hora</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] para %4$s adicionado</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] adicionado a %4$s</string>
<string name="info_bluetooth_connection_error">Ocorreu um erro inesperado com o Bluetooth</string>
<string name="info_bluetooth_init">Iniciar aparelho Bluetooh</string>
<string name="info_bluetooth_no_device">Nenhum aparelho Bluetooth encontrado</string>
@@ -135,8 +137,8 @@
<string name="label_weight_difference">Diferença de peso</string>
<string name="label_last_month">Média do mês passado</string>
<plurals name="label_days">
<item quantity="one">"dia %d "</item>
<item quantity="other">"dias %d "</item>
<item quantity="one">dia %d</item>
<item quantity="other">dias %d</item>
</plurals>
<string name="label_smartUserAssign">Identificação inteligente de utilizador</string>
<string name="label_bone">Massa óssea</string>
@@ -151,7 +153,7 @@
<string name="label_bmr">Taxa Metabólica Basal (TMB)</string>
<string name="label_bmi">Índice de Massa Corporal (IMC)</string>
<string name="label_weight">Peso</string>
<string name="label_share_subject">Exportar dados para CSV (%s)</string>
<string name="label_share_subject">Exportar dados do openScale (%s)</string>
<string name="label_share">Partilhar</string>
<string name="label_add_user">Adicionar utilizador</string>
<string name="label_delete">Apagar</string>
@@ -171,4 +173,128 @@
<string name="title_table">Tabela</string>
<string name="title_graph">Gráfico</string>
<string name="title_overview">Visão geral</string>
<string name="app_intro_next_button">Próximo</string>
<string name="permission_bluetooth_info">Permissão de Localização necessária para buscar aparelhos Bluetooth. Pode ser revogada após encontrar aparelho.</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Um erro inesperado ocorreu.
\n
\nFavor criar um novo issue incluindo detalhes do erro em
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="label_feedback_message_issue">Qual é a sua opinião sobre o app\?</string>
<string name="label_trend">Tendência</string>
<string name="label_prediction">Previsão</string>
<string name="label_trend_line">Linha de tendência</string>
<string name="info_bluetooth_connection_error_scale_offline">Sem conexão com balança, certifique-se que está ligada.</string>
<string name="info_bluetooth_connection_successful">Conectado</string>
<string name="info_bluetooth_no_device_set">Selecione um aparelho Bluetooth</string>
<string name="info_bluetooth_connection_lost">Conexão Bluetooth perdida</string>
<string name="info_scale_low_battery">Bateria baixa (%d%%), favor recarregar ou trocar as baterias da balança</string>
<string name="info_assisted_weighing_choose_reference_user">Escolha o utilizador-referência cujo peso será descontado</string>
<string name="info_assisted_weighing_no_reference_user">Sem utilizador-referência disponível, favor criar um</string>
<string name="info_assisted_weighing_old_reference_measurement">Alerta: última medida de referência tem mais de um dia</string>
<string name="info_assisted_weighing_no_reference_measurements">Utilizador-referência sem medidas</string>
<string name="info_data_imported">Importado de</string>
<string name="info_data_exported">Exportado para</string>
<string name="error_goal_weight_required">Meta de peso requerida</string>
<string name="error_initial_weight_required">Peso inicial requerido</string>
<string name="error_height_required">Altura requerida</string>
<string name="error_user_name_required">Nome requerido</string>
<string name="error_value_range">Valor fora do intervalo</string>
<string name="label_amputation_right">Amputação direita</string>
<string name="label_amputation_left">Amputação esquerda</string>
<string name="label_amputation">Amputação</string>
<string name="label_assisted_weighing">Pesagem assistida</string>
<string name="label_age">Idade</string>
<string name="label_water">Água corporal total</string>
<string name="info_bluetooth_no_device_retrying">Não foi possível conectar, a tentar novamente…</string>
<string name="error_exporting">Não foi possível exportar</string>
<string name="label_last_week">Média da semana passada</string>
<string name="error_importing">Não foi possível importar</string>
<string name="default_value_reminder_notify_text">Hora de pesar</string>
<string name="label_ignoreOutOfRange">Ignorar dados fora do intervalo</string>
<string name="label_auto_backup_schedule">Agendamento de backup</string>
<string name="label_weekly">semanalmente</string>
<string name="label_your_bluetooth_scale">A sua balança Bluetooth</string>
<string name="label_select_measurement">Selecionar medição</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Informação do erro</string>
<string name="permission_location_service_info">Conceda acesso à localização nas configurações do Android para procurar dispositivos Bluetooth. Opcionalmente, pode revogar posteriormente.</string>
<string name="permission_read_write_data_label">Ler/gravar dados do openScale</string>
<string name="label_is_on_right_axis">Está no eixo direito</string>
<string name="label_auto_backup">Backup automático</string>
<string name="label_overwrite_backup">Substituir backup anterior</string>
<string name="label_daily">diariamente</string>
<string name="label_monthly">mensal</string>
<string name="label_scale_not_supported">A sua balança não é suportada\?</string>
<string name="label_development">Desenvolvimento</string>
<string name="label_debug_log">Guardar registo de depuração para um ficheiro</string>
<string name="amputation_level_arm">Braço</string>
<string name="label_upgrade_to_openScale_pro">Por favor, atualize para openScale pro para suporte a Bluetooth</string>
<string name="amputation_level_foot"></string>
<string name="label_slide_welcome_top_text">Bem-vindo ao
\nopenScale</string>
<string name="label_slide_welcome_main_text">Software de código aberto de rastreio de peso e métricas corporais, com suporte a balanças Bluetooth.</string>
<string name="app_intro_back_button">Voltar</string>
<string name="label_slide_opensource_main_text">openScale está licenciado sob a <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
\n
\nTodos os cálculos do corpo são transparentes e nenhuma função oculta de transferência de dados ou identificação do utilizador é realizada.
\n
\nPode encontrar o código-fonte completo em <a href="https://github.com/oliexdev/openScale">GitHub</a>. Convença-se sobre o openScale, revendo-o.</string>
<string name="label_slide_metrics_main_text">openScale suporta mais de 22 métricas corporais.
\n
\nConfigure-o para mostrar aquelas que lhe interessam.
\n
\n Se a gordura corporal, água corporal e massa magra estiverem em falta, estas podem ser estimadas com base em <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">publicações científicas</a>.</string>
<string name="label_slide_support_main_text">openScale precisa de si. Crie um <a href="https://github.com/oliexdev/openScale/issues">novo issue</a> se encontrou um bug, tem uma ideia, pergunta, ou quer ajudar a suportar a sua balança Bluetooth.
\n
\nMotive o criador deste projeto para mais desenvolvimentos e manutenção contínua, dando ao openScale uma avaliação positiva no <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> ou uma estrela no <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nO seu apoio e feedback positivo são muito apreciados. Obrigado.</string>
<string name="label_slide_privacy_main_text">openScale não envia nenhuns dados para a nuvem e não ter a permissão de acesso à Internet é uma forte garantia disso.
\n
\nSe quiser realmente sincronizar o seu peso com o GoogleFit ou usando MQTT, pode instalar <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_tdee">Gasto energético diário total (TDEE)</string>
<string name="label_neck">Circunferência do pescoço</string>
<string name="info_select_scale_user">Selecione o utilizador</string>
<string name="info_enter_consent_code_for_scale_user">Introduza o código PIN/consentimento para o utilizador da balança %s</string>
<string name="label_click_to_help_add_support">Clique aqui para ajudar a adicionar apoio para o mesmo</string>
<string name="label_select_user">Selecione o utilizador</string>
<string name="label_configure_widget">Configurar widget</string>
<string name="label_visceral_fat">Gordura visceral</string>
<string name="label_chest">Circunferência do peito</string>
<string name="label_thigh">Circunferência da coxa</string>
<string name="label_biceps">Circunferência do bíceps</string>
<string name="label_calories">Calorias</string>
<string name="label_caliper2_male">Dobra da pele abdominal</string>
<string name="label_caliper1_male">Dobra da pele do peito</string>
<string name="label_caliper3_male">Dobra da pele da coxa</string>
<string name="label_caliper1_female">Dobra da pele do tríceps</string>
<string name="label_caliper2_female">Dobra de pele abdominal</string>
<string name="label_caliper3_female">Dobra da pele da anca</string>
<string name="label_measure_unit">Unidade de medida</string>
<string name="label_activity_level">Nível de atividade</string>
<string name="activity_level_sedentary">Sedentário</string>
<string name="activity_level_mild">Suave</string>
<string name="activity_level_moderate">Moderado</string>
<string name="activity_level_heavy">Pesado</string>
<string name="activity_level_extreme">Extremo</string>
<string name="amputation_level_none">Sem amputação</string>
<string name="amputation_level_hand">Mão</string>
<string name="amputation_level_forearm_hand">Antebraço e perna</string>
<string name="amputation_level_lower_leg_foot">Parte inferior da perna e pé</string>
<string name="amputation_level_leg">Perna</string>
<string name="label_fat_caliper">Pinça de medição de gordura corporal</string>
<string name="label_slide_privacy_top_text">Proteja os seus dados pessoais de saúde.</string>
<string name="label_slide_user_top_text">São suportados múltiplos utilizadores.</string>
<string name="label_slide_user_main_text">openScale não requer a criação de uma conta online.
\n
\nAs métricas corporais são calculadas a partir das medições.</string>
<string name="label_slide_opensource_top_text">É um software de código aberto com um elevado grau de transparência.</string>
<string name="label_slide_bluetooth_top_text">São suportadas várias balanças Bluetooth de diferentes fabricantes.</string>
<string name="label_slide_metrics_top_text">Acompanhe e analise as métricas do seu corpo.</string>
<string name="label_slide_support_top_text">Ajude a melhorar o openScale.</string>
<string name="app_intro_skip_button">Saltar</string>
<string name="app_intro_done_button">Concluído</string>
<string name="info_create_new_user_on_scale">Criar um novo utilizador na balança.</string>
<string name="label_slide_bluetooth_main_text">O openScale suporta várias balanças Bluetooth de diferentes fabricantes.
\n
\nMelhoramos constantemente e alargamos o conjunto de balanças suportadas. Pode encontrar a lista completa e o nível de apoio para cada uma em <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
</resources>

View File

@@ -1,5 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="title_overview">Rezumat</string>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_overview">Rezumat</string>
<string name="title_graph">Grafic</string>
<string name="title_table">Tabel</string>
<string name="title_statistics">Statistici</string>
@@ -7,12 +8,10 @@
<string name="title_measurements">Măsurători</string>
<string name="title_about">Despre</string>
<string name="title_general">General</string>
<string name="action_settings">Parametrare</string>
<string name="action_bluetooth_status">"Situația Bluetooth "</string>
<string name="action_bluetooth_status">Situația Bluetooth</string>
<string name="open_drawer">deschide</string>
<string name="close_drawer">inchide</string>
<string name="label_cancel">Anulare</string>
<string name="label_ok">OK</string>
<string name="label_yes">Da</string>
@@ -20,13 +19,12 @@
<string name="label_delete">Ștergere</string>
<string name="label_add_user">Adăugați un utilizator</string>
<string name="label_share">Distribuiți</string>
<string name="label_share_subject">"Exportați ca și fișier CSV (%s) "</string>
<string name="label_share_subject">Exportați ca și fișier CSV (%s)</string>
<string name="label_weight">Greutate</string>
<string name="label_bmi">"Indice de masă corporală (IMC) "</string>
<string name="label_bmi">Indice de masă corporală (IMC)</string>
<string name="label_bmr">Metabolism bazal (BMR)</string>
<string name="label_fat">Țesut adipos</string>
<string name="label_water">Apa din organism</string>
<string name="label_water">Apa corporală totală</string>
<string name="label_muscle">Masa musculară</string>
<string name="label_lbm">Greutate în absența grăsimii</string>
<string name="label_waist">Circumferința la nivelul taliei</string>
@@ -36,18 +34,16 @@
<string name="label_whr">Raport talie-coapse</string>
<string name="label_bone">Masa osoasă</string>
<string name="label_smartUserAssign">Atribuire inteligentă la in utilizator</string>
<plurals name="label_days">
<item quantity="one">%d zi</item>
<item quantity="few">%d zile</item>
<item quantity="other">%d zile</item>
</plurals>
<string name="label_last_week">Ultimele 7 zile</string>
<string name="label_last_month">Ultimele 30 de zile</string>
<string name="label_last_week">Media în ultima săptămână</string>
<string name="label_last_month">Media în ultima lună</string>
<string name="label_weight_difference">Diferența de greutate</string>
<string name="label_goal_date_is">Data atingerii obiectivului:</string>
<string name="label_days_left">Zile rămase</string>
<string name="label_date">Data</string>
<string name="label_time">Ora</string>
<string name="label_birthday">Data nașterii</string>
@@ -59,24 +55,20 @@
<string name="label_female">Femeie</string>
<string name="label_goal_weight">Greutatea dorită</string>
<string name="label_goal_date">Data atingerii obiectivului</string>
<string name="label_title_user">utilizator</string>
<string name="label_title_last_measurement">ultima măsurătoare</string>
<string name="label_title_goal">obiectiv</string>
<string name="label_title_goal">Obiectiv</string>
<string name="label_import">Importare</string>
<string name="label_export">Exportare</string>
<string name="label_delete_all">Ștergere completă</string>
<string name="error_value_required">"O valoare e necesară "</string>
<string name="error_value_range">Valoare în afara limitelor permise</string>
<string name="error_exporting">Eroare la export</string>
<string name="error_importing">Eroare la import</string>
<string name="error_user_name_required">Eroare: Nume necesar</string>
<string name="error_height_required">Eroare: Înălțime necesară</string>
<string name="error_initial_weight_required">Eroare: Greutatea inițială trebuie indicată</string>
<string name="error_goal_weight_required">Eroare: Greutatea dorită trebuie indicată</string>
<string name="error_exporting">Nu s-a putut exporta</string>
<string name="error_importing">Nu s-a putut importa</string>
<string name="error_user_name_required">Nume necesar</string>
<string name="error_height_required">Înălțime necesară</string>
<string name="error_initial_weight_required">Greutatea inițială trebuie indicată</string>
<string name="error_goal_weight_required">Greutatea dorită trebuie indicată</string>
<string name="info_data_deleted">Înregistrare ștearsă</string>
<string name="info_data_all_deleted">Toate înregistrările au fost șterse</string>
<string name="info_data_exported">Exportat spre</string>
@@ -91,42 +83,32 @@
<string name="info_bluetooth_try_connection">Conectare la:</string>
<string name="info_bluetooth_connection_lost">Conexiune Bluetooth pierdută</string>
<string name="info_bluetooth_no_device">Nici un dispozitiv Bluetooth detectat</string>
<string name="info_bluetooth_no_device_set">Nici un dispozitiv Bluetooth selecționat</string>
<string name="info_bluetooth_connection_successful">Conexiune stabilită</string>
<string name="info_bluetooth_no_device_set">Selectați un dispozitiv Bluetooth</string>
<string name="info_bluetooth_connection_successful">Conectat</string>
<string name="info_bluetooth_init">Inițializare dispozitiv Bluetooth</string>
<string name="info_bluetooth_connection_error">"Eroare Bluetooth "</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] până la %4$s adăugate</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] adăugate la %4$s</string>
<string name="info_new_data_duplicated">măsurători la aceeași dată și oră deja realizate</string>
<string name="info_enter_user_name">Numele dumneavoastră</string>
<string name="info_no_selected_user">Nu s-a setat nici un utilizator. Vă rugăm creați unul în secțiunea parametri.</string>
<string name="info_no_evaluation_available">Valoarea nu a putut fi evaluată</string>
<string name="question_really_delete">Ștergere a înregistrării?</string>
<string name="question_really_delete_all">Ștergere a tuturor înregistrărilor pentru toți utilizatorii?</string>
<string name="question_really_delete_user">Ștergere utilizator?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Conectare la cântar la pornire</string>
<string name="label_mergeWithLastMeasurement">Adaugă la ultima măsurătoare</string>
<string name="label_bluetooth_searching">În curs de căutare a unui cântar Bluetooth</string>
<string name="label_bluetooth_searching_finished">Căutare încheiată</string>
<string name="label_enable_labels">Etichetare date</string>
<string name="label_enable_points">Poziție</string>
<string name="label_delete_confirmation">Confirmare a ștergerii</string>
<string name="label_category_measurement_database">Baza de date cu măsurătorile</string>
<string name="label_maintainer">Programator</string>
<string name="label_website">Sit Internet</string>
<string name="label_license">Licență</string>
<string name="label_automatic">auto</string>
<string name="label_theme">Temă</string>
<string name="label_reminder">Alarmă</string>
<string name="Thursday">Joi</string>
<string name="Friday">Vineri</string>
@@ -142,7 +124,6 @@
<string name="label_initial_weight">Greutate inițială</string>
<string name="label_goal_line">Linie pentru obiectiv</string>
<string name="label_help">Ajutor</string>
<string name="label_feedback_message_enjoying">Satisfăcut de openScale?</string>
<string name="label_feedback_message_rate_app">Lăsați o evaluare pe Google Play sau GitHub?</string>
<string name="label_feedback_message_issue">Doriți să furnizați comentarii despre aplicație?</string>
@@ -150,11 +131,9 @@
<string name="label_feedback_message_no">Nu neapărat</string>
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">Nu, mulțumesc</string>
<string name="error_max_scale_users">Numărul maxim de utilizatori al unui același aparat depășit</string>
<string name="info_step_on_scale_for_reference">Urcați vă rog pe cântar desculț pentru măsurătorile de referință</string>
<string name="info_measuring">Măsurare a greutății în curs: %.2f</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">O eroare a apărut.
\n
\nVă rugăm să adăugați un comentariu cu detaliile erorii la
@@ -170,22 +149,18 @@
<string name="toggle_expand">Activarea modului extins</string>
<string name="edit">Modificare</string>
<string name="save">Salvare</string>
<string name="label_month_view">Vizualizare a lunii</string>
<string name="label_weeks_view">Vizualizare a săptămânii</string>
<string name="permission_not_granted">Permisiune neacordată</string>
<string name="permission_bluetooth_info">Permisiunea accesului la localizare e necesară pentru a căuta dispozitive Bluetooth. Permisiunea poate fi revocată o dată ce dispozitivul a fost găsit.</string>
<string name="permission_bluetooth_info_title">Informație</string>
<string name="label_next">Următoarea</string>
<string name="label_set_default_order">Definire a ordinii de bază</string>
<string name="label_reminder_weekdays">Zile lucrătoare</string>
<string name="label_reminder_weekdays">Zile</string>
<string name="label_reminder_time">Ora</string>
<string name="label_reminder_notify_text">Textul notificării</string>
<string name="default_value_reminder_notify_text">E momentul de a se cântări</string>
<string name="info_on_date">în</string>
<string name="Monday">Luni</string>
<string name="Tuesday">Marți</string>
<string name="Wednesday">Miercuri</string>
@@ -212,4 +187,20 @@
<string name="label_click_to_help_add_support">Clic aici pentru a ajuta la adăugare printre dispozitivele cunoscute</string>
<string name="label_development">Programare</string>
<string name="label_debug_log">Înregistrarea datelor de diagnostic într-un fișier</string>
</resources>
<string name="info_assisted_weighing_no_reference_measurements">Utilizatorul de referință nu are nicio măsurătură</string>
<string name="info_assisted_weighing_old_reference_measurement">Atenție: ultima măsurătură de referință e mai veche de o zi</string>
<string name="action_donation">Donație</string>
<string name="label_amputation">Amputare</string>
<string name="info_assisted_weighing_choose_reference_user">Alegeți utilizatorul de referință din care masa este scăzută</string>
<string name="info_bluetooth_connection_error_scale_offline">Nu s-a putut conecta la cântar, vă rugăm asigurați-vă că este pornit.</string>
<string name="label_amputation_right">Amputare dreapta</string>
<string name="info_assisted_weighing_no_reference_user">Niciun utilizator de referință disponibil, vă rugăm creați unul</string>
<string name="info_bluetooth_connection_disconnected">Conexiune Bluetooth închisă</string>
<string name="info_bluetooth_no_device_retrying">Nu s-a putut creea conexiunea, reîncercare…</string>
<string name="info_scale_low_battery">Nivel baterie scăzit (%d%%), vă rugăm să încărcați sau să înlocuiți bateriile</string>
<string name="label_amputation_left">Amputare stânga</string>
<string name="label_empty">gol</string>
<string name="label_age">Vârstă</string>
<string name="label_assisted_weighing">Cântărire asistată</string>
<string name="label_add_measurement">Adăugare măsurătură</string>
</resources>

View File

@@ -20,7 +20,7 @@
<string name="label_add_user">Добавить пользователя</string>
<string name="label_add_measurement">Добавить измерение</string>
<string name="label_share">Поделиться</string>
<string name="label_share_subject">Экспорт данных CSV в (%s)</string>
<string name="label_share_subject">экспорт данных openScale (%s)</string>
<string name="label_weight">Вес</string>
<string name="label_bmi">Индекс массы тела (ИМТ)</string>
<string name="label_bmr">Основной обмен (ОО)</string>
@@ -290,4 +290,10 @@
<string name="info_assisted_weighing_old_reference_measurement">Предупреждение: последнее контрольное измерение старше суток</string>
<string name="info_assisted_weighing_no_reference_measurements">Контрольный пользователь не имеет измерений</string>
<string name="label_assisted_weighing">Вспомогательное взвешивание</string>
<string name="label_trend_line">Линия тренда</string>
<string name="label_trend">Тренд</string>
<string name="label_prediction">Прогноз</string>
<string name="info_select_scale_user">Выберите пользователя весов</string>
<string name="info_enter_consent_code_for_scale_user">Введите PIN-код для пользователя весов %s</string>
<string name="info_create_new_user_on_scale">Создайте нового пользователя весов.</string>
</resources>

View File

@@ -143,7 +143,8 @@
<string name="label_feedback_message_negative">Nie, ďakujem</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Vyskytla sa neočakávaná chyba.
\n
\nVytvorte nové hlásenie o chybách na https://github.com/oliexdev/openScale/issues , kde uvediete aj podrobnosti o chybe.</string>
\nVytvorte nové hlásenie o chybách na kde uvediete aj podrobnosti o chybe
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Reštartovať aplikáciu</string>
<string name="customactivityoncrash_error_activity_close_app">Zavrieť aplikáciu</string>
<string name="customactivityoncrash_error_activity_error_details">Podrobnosti chyby</string>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="label_days">
<item quantity="one">%d дан</item>
<item quantity="few">%d дана</item>
<item quantity="other">%d дана</item>
</plurals>
<string name="title_about">О програму</string>
<string name="title_graph">Графикон</string>
<string name="title_statistics">Статистика</string>
<string name="title_users">Корисници</string>
<string name="action_bluetooth_status">Стање блутута</string>
<string name="label_ok">У реду</string>
<string name="label_add_user">Додај корисника</string>
<string name="label_share_subject">openScale извоз података (%s)</string>
<string name="label_bmi">Индекс телесне масе (BMI)</string>
<string name="label_bmr">Базални метаболизам (BMR)</string>
<string name="label_tdee">Укупни дневни утрошак енергије (TDEE)</string>
<string name="label_fat">Масноћа тела</string>
<string name="label_water">Вода у телу</string>
<string name="label_muscle">Мишићи</string>
<string name="label_lbm">Телесна маса без масти</string>
<string name="label_waist">Пречник струка</string>
<string name="label_hip">Пречник кукова</string>
<string name="label_user_name">Име</string>
<string name="label_height">Висина</string>
<string name="error_user_name_required">Име је потребно</string>
<string name="error_height_required">Висина је потребна</string>
<string name="error_initial_weight_required">Почетна тежина је потребна</string>
<string name="error_goal_weight_required">Жељена тежина је потребна</string>
<string name="info_data_deleted">Унос избрисан</string>
<string name="info_data_all_deleted">Сви уноси су избрисани</string>
<string name="info_data_imported">Увезено из</string>
<string name="info_enter_value_in">Вредност у</string>
<string name="info_enter_comment">Коментар по избору</string>
<string name="info_is_visible">је видљиво</string>
<string name="info_is_not_available">није доступно</string>
<string name="title_table">Табела</string>
<string name="open_drawer">отвори</string>
<string name="label_cancel">Откажи</string>
<string name="label_add_measurement">Додај мерење</string>
<string name="label_weight">Тежина</string>
<string name="title_overview">Преглед</string>
<string name="title_measurements">Мерења</string>
<string name="title_general">Опште</string>
<string name="label_no">Не</string>
<string name="label_empty">празно</string>
<string name="label_share">Подели</string>
<string name="label_last_week">Просек прошле недеље</string>
<string name="action_settings">Подешавања</string>
<string name="label_whtr">Однос струка са висином</string>
<string name="action_donation">Донација</string>
<string name="close_drawer">затвори</string>
<string name="label_delete">Обриши</string>
<string name="label_bone">Маса костију</string>
<string name="label_last_month">Просек прошлог месеца</string>
<string name="label_date">Датум</string>
<string name="label_export">Извези</string>
<string name="label_yes">Да</string>
<string name="label_comment">Коментар</string>
<string name="label_whr">Однос струка са куковима</string>
<string name="label_goal_date_is">Циљни датум:</string>
<string name="label_time">Време</string>
<string name="label_age">Старост</string>
<string name="label_weight_difference">Тежинска разлика</string>
<string name="label_days_left">Дана преостало</string>
<string name="label_goal_weight">Жељена тежина</string>
<string name="label_title_goal">Циљ</string>
<string name="label_delete_all">Обриши све</string>
<string name="label_birthday">Рођендан</string>
<string name="label_scale_unit">Мерна јединица</string>
<string name="label_gender">Пол</string>
<string name="label_amputation">Ампутација</string>
<string name="label_male">Мушко</string>
<string name="label_female">Женско</string>
<string name="label_goal_date">Жељени датум</string>
<string name="label_title_user">корисник</string>
<string name="label_title_last_measurement">последње мерење</string>
<string name="label_import">Увези</string>
<string name="error_value_required">Обавезна вредност</string>
<string name="error_exporting">Не могу да извезем</string>
<string name="error_importing">Не могу да увезем</string>
<string name="info_data_exported">Извезено у</string>
<string name="error_value_range">Вредност ван опсега</string>
<string name="info_is_not_visible">није видљиво</string>
<string name="info_is_enable">омогућено</string>
<string name="info_is_not_enable">онемогућено</string>
</resources>

View File

@@ -19,7 +19,7 @@
<string name="label_bmi">Kroppsmasseindex (BMI)</string>
<string name="label_bmr">Basal ämnesomsättning (BMR)</string>
<string name="label_fat">Kroppsfett</string>
<string name="label_water">Kroppsvatten</string>
<string name="label_water">Totalt kroppsvatten</string>
<string name="Monday">Måndag</string>
<string name="Tuesday">Tisdag</string>
<string name="Wednesday">Onsdag</string>
@@ -33,7 +33,7 @@
<string name="label_reminder_weekdays">Dagar</string>
<string name="label_website">Hemsida</string>
<string name="label_license">Licens</string>
<string name="error_exporting">Fel vid export</string>
<string name="error_exporting">Kunde inte exportera</string>
<string name="label_gender">Kön</string>
<string name="label_male">Man</string>
<string name="label_female">Kvinna</string>
@@ -52,8 +52,8 @@
<string name="label_goal_date_is">Måldatum:</string>
<string name="label_weight_difference">Viktskillnad</string>
<plurals name="label_days">
<item quantity="one">%d dag</item>
<item quantity="other">%d dagar</item>
<item quantity="one">dag %d</item>
<item quantity="other">dagar %d</item>
</plurals>
<string name="label_last_week">Medelvärde senaste veckan</string>
<string name="label_last_month">Medelvärde senaste månaden</string>
@@ -71,11 +71,11 @@
<string name="label_delete_all">Ta bort alla</string>
<string name="error_value_required">Värde krävs</string>
<string name="error_value_range">Värde utanför intervall</string>
<string name="error_importing">Fel vid import</string>
<string name="error_user_name_required">Fel: namn krävs</string>
<string name="error_height_required">Fel: längd krävs</string>
<string name="error_initial_weight_required">Fel: startvikt krävs</string>
<string name="error_goal_weight_required">Fel: målvikt krävs</string>
<string name="error_importing">Kunde ej importera</string>
<string name="error_user_name_required">Namn krävs</string>
<string name="error_height_required">Längd krävs</string>
<string name="error_initial_weight_required">Startvikt krävs</string>
<string name="error_goal_weight_required">Målvikt krävs</string>
<string name="info_data_deleted">Post borttagen</string>
<string name="info_data_all_deleted">Alla poster borttagna</string>
<string name="info_data_exported">Exporterad till</string>
@@ -90,9 +90,9 @@
<string name="info_bluetooth_try_connection">Ansluter till:</string>
<string name="info_bluetooth_connection_lost">Förlorade Bluetooth-anslutningen</string>
<string name="info_bluetooth_no_device">Ingen Bluetooth-enhet hittades</string>
<string name="info_bluetooth_connection_successful">Anslutning etablerad</string>
<string name="info_bluetooth_connection_successful">Ansluten</string>
<string name="info_bluetooth_init">Initiera Bluetooth-enhet</string>
<string name="info_bluetooth_connection_error">Oväntat Bluetooth fel</string>
<string name="info_bluetooth_connection_error">Oväntat Bluetooth-fel</string>
<string name="info_new_data_added">La till %1$.2f%2$s [%3$s] till %4$s</string>
<string name="info_enter_user_name">Ditt namn</string>
<string name="info_no_selected_user">Ingen användare finns. Skapa en i inställningarna.</string>
@@ -125,7 +125,7 @@
<string name="info_measuring">Mäter vikt: %.2f</string>
<string name="open_drawer">öppna</string>
<string name="close_drawer">stäng</string>
<string name="info_bluetooth_no_device_set">Ingen Bluetooth-enhet vald</string>
<string name="info_bluetooth_no_device_set">Välj Bluetooth-enhet</string>
<string name="info_new_data_duplicated">mätning med samma datum och tid existerar redan</string>
<string name="title_general">Allmänt</string>
<string name="label_theme">Tema</string>
@@ -155,10 +155,10 @@
<string name="toggle_expand">Växla expandera</string>
<string name="edit">Ändra</string>
<string name="label_month_view">Månadsvy</string>
<string name="permission_not_granted">Tillstånd beviljas inte</string>
<string name="permission_bluetooth_info">Platsbehörighet krävs för att söka efter Bluetooth-enheter. Det kan återkallas när enheten har hittats.</string>
<string name="permission_not_granted">Behörighet saknas</string>
<string name="permission_bluetooth_info">Platsbehörighet krävs för att söka efter Bluetooth-enheter. Den kan återkallas när enheten har hittats.</string>
<string name="permission_bluetooth_info_title">Info</string>
<string name="label_share_subject">CSV dataexport (%s)</string>
<string name="label_share_subject">openScale dataexport (%s)</string>
<string name="label_next">Nästa</string>
<string name="label_set_default_order">Ställ in standardordning</string>
<string name="label_weeks_view">Veckovy</string>
@@ -212,8 +212,8 @@
<string name="activity_level_heavy">Hård</string>
<string name="activity_level_extreme">Extrem</string>
<string name="permission_read_write_data_description">läs openScale-data, inklusive användarinformation och alla sparade mätningar</string>
<string name="permission_read_write_data_label">Läs openScale-data</string>
<string name="label_tdee">Totala dagliga energiförbrukningen (TDEE)</string>
<string name="permission_read_write_data_label">Läs/Skriv openScale-data</string>
<string name="label_tdee">Total daglig energiförbrukning (TDEE)</string>
<string name="trisa_scale_not_paired">Vågen har inte parats!
\n
\nHåll in knappen under vågen för att växla till parningsläge, och anslut sedan igen för att hämta enhetens lösenord.</string>
@@ -223,4 +223,74 @@
<string name="label_calories">Kalorier</string>
<string name="label_upgrade_to_openScale_pro">Vänligen uppgradera till openScale pro för Bluetooth-stöd</string>
<string name="action_donation">Donation</string>
<string name="label_age">Ålder</string>
<string name="label_empty">tom</string>
<string name="info_assisted_weighing_old_reference_measurement">Varning: senaste referensmätning är äldre än en dag</string>
<string name="label_prediction">Prognos</string>
<string name="label_trend">Tendens</string>
<string name="info_step_on_scale">Var god kliv upp barfota på vågen</string>
<string name="label_trend_line">Trendlinje</string>
<string name="info_select_scale_user">Välj våganvändare</string>
<string name="label_is_on_right_axis">Är på den högra axeln</string>
<string name="label_slide_welcome_top_text">Välkommen till
\nopenScale</string>
<string name="label_year_view">Årsöversikt</string>
<string name="amputation_level_hand">Hand</string>
<string name="label_slide_privacy_main_text">openScale skickar inga data till något moln och att det saknar behörighet till internetåtkomst är en stark garanti för det.
\n
\nOm du verkligen vill synkronisera din vikt med GoogleFit eller med MQTT kan du installera <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="permission_location_service_info">Bevilja platsbehörighet i Android-inställningarna för att söka efter Bluetooth-enheter. Om du vill kan du dra tillbaka behörigheten efteråt.</string>
<string name="label_slide_opensource_main_text">openScale är publicerat under licensen <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
\n
\nAlla kroppsrelaterade beräkningar är transparenta och ingen dold dataöverföring eller identifikation av användarna utförs.
\n
\nDu hittar den fullständiga källkoden på <a href="https://github.com/oliexdev/openScale">GitHub</a>. Få förtroende för openScale genom att studera den.</string>
<string name="info_create_new_user_on_scale">Skapa ny användare av vågen.</string>
<string name="label_slide_support_main_text">openScale behöver dig. Skapa ett <a href="https://github.com/oliexdev/openScale/issues">nytt ärende</a> om du hittar ett fel, har en idé, fråga eller vill hjälpa till att införa stöd för din Bluetooth-våg.
\n
\nMotivera skaparen av det här projektet till fortsatt utveckling och fortlöpande underhåll genom att ge openScale ett positivt omdöme på <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> eller stjärnmärk det på <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nDitt stöd och positiva återkoppling är varmt uppskattade. Stort tack.</string>
<string name="label_enable_yaxis">Y-axel</string>
<string name="info_bluetooth_connection_disconnected">Bluetoothanslutning avslutad</string>
<string name="info_bluetooth_connection_error_scale_offline">Kunde inte ansluta till vågen, var god kontrollera att den är på.</string>
<string name="info_bluetooth_no_device_retrying">Kunde inte upprätta en anslutning, försöker igen…</string>
<string name="info_scale_low_battery">Låg batterinivå (%d%%), var god ladda eller byt ut vågens batterier</string>
<string name="info_assisted_weighing_no_reference_user">Ingen referensanvändare tillgänglig, var god skapa en</string>
<string name="info_assisted_weighing_no_reference_measurements">Referensanvändaren har inga mätvärden</string>
<string name="label_amputation_left">Amputation vänster</string>
<string name="label_amputation_right">Amputation höger</string>
<string name="label_amputation">Amputation</string>
<string name="label_day_view">Dagsöversikt</string>
<string name="amputation_level_forearm_hand">Underarm och ben</string>
<string name="amputation_level_arm">Arm</string>
<string name="amputation_level_foot">Fot</string>
<string name="amputation_level_lower_leg_foot">Underben och fot</string>
<string name="amputation_level_leg">Ben</string>
<string name="label_slide_privacy_top_text">Skydda din personliga hälsodata.</string>
<string name="label_slide_user_top_text">Har stöd för flera användare.</string>
<string name="label_slide_user_main_text">openScale kräver inte att du skapar ett onlinekonto.
\n
\nKroppsdata beräknas utifrån mätvärden.</string>
<string name="label_slide_opensource_top_text">Programvara med öppen källkod och hög grad av transparens.</string>
<string name="amputation_level_none">Ingen amputation</string>
<string name="label_slide_bluetooth_top_text">Diverse Bluetooth-vågar från olika tillverkare stöds.</string>
<string name="label_slide_bluetooth_main_text">openScale har inbyggt stöd för ett antal Bluetooth-vågar från olika tillverkare.
\n
\nVi förbättrar och utökar ständigt raden av vågar som stöds. Du finner den fullständiga listan och till vilken grad varje våg stöds på <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_slide_metrics_top_text">Mät och beräkna fortlöpande din kroppsdata.</string>
<string name="label_slide_support_top_text">Hjälp till att förbättra openScale.</string>
<string name="app_intro_skip_button">Hoppa över</string>
<string name="app_intro_next_button">Nästa</string>
<string name="app_intro_back_button">Tillbaka</string>
<string name="app_intro_done_button">Klar</string>
<string name="label_slide_metrics_main_text">openScale har stöd för över 22 sorters kroppsdata.
\n
\nKonfigurera appen så att den visar den data du bryr dig om.
\n
\n Om kroppsfett, kroppsvatten och fettfri kroppsvikt saknas kan de uppskattas baserat på <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">vetenskapligt publicerade modeller</a>.</string>
<string name="label_assisted_weighing">Assisterad vägning</string>
<string name="label_enable_legend">Graflegend</string>
<string name="info_assisted_weighing_choose_reference_user">Välj referensanvändaren som vikten dras av</string>
<string name="info_enter_consent_code_for_scale_user">Ange PIN/samtyckeskod för våganvändaren %s</string>
</resources>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label_water">மொத்த உடல் நீர்</string>
<string name="label_fat">உடல் கொழுப்பு</string>
<string name="label_bmr">அடிப்படை வளர்சிதைமாற்ற வீதம் (பி.எம்.ஆர்)</string>
<string name="label_bmi">உடலெடைச் சுட்டெண் (பி.எம்.ஐ)</string>
<string name="label_add_measurement">ஒரு அளவினைச் சேர்</string>
<string name="action_bluetooth_status">புளூடூத் நிலை</string>
<string name="open_drawer">திற</string>
<string name="title_measurements">அளவுகள்</string>
<string name="title_about">மேலும்</string>
<string name="label_slide_welcome_top_text">openScale
\nஇற்கு வருக</string>
<string name="amputation_level_leg">கால்</string>
<string name="amputation_level_hand">கை</string>
<string name="activity_level_extreme">எல்லை மீறி</string>
<string name="activity_level_heavy">அதிகமாக</string>
<string name="activity_level_moderate">சராசரி</string>
<string name="activity_level_mild">குறைந்தது</string>
<string name="label_calories">கலொரிகள்</string>
<string name="label_select_user">பயனரைத் தேர்ந்தெடு</string>
<string name="label_monthly">ஒவ்வொரு மாதமும்</string>
<string name="label_weekly">ஒவ்வொரு வாரமும்</string>
<string name="label_daily">தினமும்</string>
<string name="label_estimated">மதிப்பிடப்பட்டது</string>
<string name="label_percent">சதவிகிதம்</string>
<string name="theme_dark">கருப்பு</string>
<string name="theme_light">வெள்ளை</string>
<string name="label_language">மொழி</string>
<string name="label_next">அடுத்தது</string>
<string name="permission_bluetooth_info_title">தகவல்</string>
<string name="label_year_view">ஆண்டுப் பார்வை</string>
<string name="label_weeks_view">வாரப் பார்வை</string>
<string name="label_month_view">மாதப் பார்வை</string>
<string name="label_day_view">நாள் பார்வை</string>
<string name="save">பதிவிடு</string>
<string name="edit">தொகு</string>
<string name="customactivityoncrash_error_activity_error_details_close">மூடு</string>
<string name="label_feedback_message_no">இல்லை</string>
<string name="label_feedback_message_yes">ஆம்</string>
<string name="label_not_found">கிடைக்கவில்லை</string>
<string name="Wednesday">புதன்கிழமை</string>
<string name="Tuesday">செவ்வாய்க்கிழமை</string>
<string name="Monday">திங்கட்கிழமை</string>
<string name="label_feedback_message_negative">வேண்டாம், நன்றி</string>
<string name="label_feedback_message_positive">சரி</string>
<string name="label_help">உதவி</string>
<string name="Sunday">ஞாயிற்றுக்கிழமை</string>
<string name="Saturday">சனிக்கிழமை</string>
<string name="Friday">வெள்ளிக்கிழமை</string>
<string name="Thursday">வியாழக்கிழமை</string>
<string name="label_birthday">பிறந்த நாள்</string>
<string name="label_weight">எடை</string>
<string name="label_share">பகிர்</string>
<string name="label_add_user">பயனரை சேர்</string>
<string name="label_empty">காலி</string>
<string name="label_delete">நீக்கு</string>
<string name="label_no">இல்லை</string>
<string name="label_yes">ஆம்</string>
<string name="label_ok">சரி</string>
<string name="label_cancel">இரத்துசெய்</string>
<string name="close_drawer">மூடு</string>
<string name="action_donation">நன்கொடை</string>
<string name="action_settings">அமைப்புகள்</string>
<string name="title_general">பொது</string>
<string name="title_users">பயனர்கள்</string>
<string name="title_statistics">புள்ளிவிவரங்கள்</string>
<string name="title_table">அட்டவணை</string>
<string name="title_graph">வரைபடம்</string>
<string name="title_overview">மேற்பார்வை</string>
</resources>

View File

@@ -215,7 +215,7 @@
<string name="label_caliper2_female">Karın derisi kıvrım kalınlığı</string>
<string name="label_caliper3_female">Kalça derisi kıvrım kalınlığı</string>
<string name="activity_level_moderate">Orta derecede</string>
<string name="label_share_subject">CSV veri dışa aktarımı (%s)</string>
<string name="label_share_subject">openScale veri dışa aktarımı (%s)</string>
<string name="label_click_to_help_add_support">Yardım ekleyerek destek olmak için buraya tıklayın</string>
<string name="label_measurement_in_percent">% cinsinden ölçüm</string>
<string name="toggle_expand">Geçiş genişlemesi</string>
@@ -283,10 +283,16 @@
<string name="amputation_level_none">Ampütasyon yok</string>
<string name="info_assisted_weighing_choose_reference_user">ırlığın çıkartılacağı referans kullanıcıyı seçin</string>
<string name="info_assisted_weighing_no_reference_user">Referans kullanıcı yok, lütfen bir tane oluşturun</string>
<string name="info_assisted_weighing_old_reference_measurement">Uyarı: Son referans ölçümü bir günden daha eski</string>
<string name="info_assisted_weighing_old_reference_measurement">Uyarı: son referans ölçümü bir günden daha eski</string>
<string name="info_assisted_weighing_no_reference_measurements">Referans kullanıcının ölçümü yok</string>
<string name="label_amputation_right">Sağ ampütasyon</string>
<string name="label_amputation_left">Sol ampütasyon</string>
<string name="label_amputation">Ampütasyon</string>
<string name="label_assisted_weighing">Destekli tartım</string>
<string name="label_trend">Eğilim</string>
<string name="label_prediction">Tahmin</string>
<string name="label_trend_line">Eğilim çizgisi</string>
<string name="info_select_scale_user">Tartı kullanıcısını seçin</string>
<string name="info_enter_consent_code_for_scale_user">Tartı kullanıcısı %s için PIN/onay kodunu girin</string>
<string name="info_create_new_user_on_scale">Tartıda yeni kullanıcı oluşturun.</string>
</resources>

View File

@@ -6,10 +6,10 @@
<string name="title_statistics">Статистика</string>
<string name="title_users">Користувачі</string>
<string name="title_measurements">Виміри</string>
<string name="title_about">Про додаток</string>
<string name="title_about">Про застосунок</string>
<string name="title_general">Загальне</string>
<string name="action_settings">Налаштування</string>
<string name="action_bluetooth_status">Статус Bluetooth</string>
<string name="action_bluetooth_status">Стан Bluetooth</string>
<string name="open_drawer">відкрити</string>
<string name="close_drawer">закрити</string>
<string name="label_cancel">Скасувати</string>
@@ -20,7 +20,7 @@
<string name="label_add_user">Додати користувача</string>
<string name="label_add_measurement">Додати вимір</string>
<string name="label_share">Поділитися</string>
<string name="label_share_subject">Експорт CSV даних (%s)</string>
<string name="label_share_subject">експорт даних openScale (%s)</string>
<string name="label_weight">Вага</string>
<string name="label_bmi">Індекс маси тіла (BMI)</string>
<string name="label_bmr">Основний обмін (BMR)</string>
@@ -58,7 +58,7 @@
<string name="label_goal_weight">Мета ваги</string>
<string name="label_goal_date">Дата мети</string>
<string name="label_title_user">користувач</string>
<string name="label_title_last_measurement">крайній вимір</string>
<string name="label_title_last_measurement">останній вимір</string>
<string name="label_title_goal">Мета</string>
<string name="label_import">Імпорт</string>
<string name="label_export">Експорт</string>
@@ -82,36 +82,36 @@
<string name="info_is_enable">увімкнуто</string>
<string name="info_is_not_enable">вимкнуто</string>
<string name="info_is_not_available">не доступне</string>
<string name="info_bluetooth_try_connection">Підключення до:</string>
<string name="info_bluetooth_connection_lost">З\'єднання з Bluetouch втрачено</string>
<string name="info_bluetooth_no_device">Bluetouch пристрій не знайдено</string>
<string name="info_bluetooth_try_connection">Під\'єднання до:</string>
<string name="info_bluetooth_connection_lost">Bluetooth-з\'єднання втрачено</string>
<string name="info_bluetooth_no_device">Пристрій Bluetooth не знайдено</string>
<string name="info_bluetooth_no_device_retrying">Не вдалося встановити з\'єднання, повторна спроба…</string>
<string name="info_bluetooth_no_device_set">Оберіть пристрій Bluetooth</string>
<string name="info_bluetooth_connection_successful">З\'єднання встановлено</string>
<string name="info_bluetooth_connection_successful">Під\'єднано</string>
<string name="info_bluetooth_init">Ініціалізація Bluetooth пристрою</string>
<string name="info_bluetooth_connection_error">Неочікувана помилка Bluetooth</string>
<string name="info_bluetooth_connection_disconnected">Підключення до Bluetooth закрито</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth-з\'єднання закрито</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] додано до %4$s</string>
<string name="info_new_data_duplicated">вимір з вказаною датою та часом вже існує</string>
<string name="info_new_data_duplicated">вимір із вказаною датою та часом вже існує</string>
<string name="info_enter_user_name">Ваше ім\'я</string>
<string name="info_no_selected_user">Користувача не існує. Будь ласка, створіть користувача в налаштуваннях.</string>
<string name="info_no_selected_user">Користувача не існує. Створіть користувача в налаштуваннях.</string>
<string name="info_no_evaluation_available">Не вдалося визначити значення</string>
<string name="question_really_delete">Видалити запис\?</string>
<string name="question_really_delete_all">Видалити всі записи для всіх користувачів\?</string>
<string name="question_really_delete_user">Видалити користувача\?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Приєднуватися до вагів при старті додатку</string>
<string name="label_mergeWithLastMeasurement">Об\'єднати з крайнім виміром</string>
<string name="label_bluetooth_enable">Під\'єднуватися до вагів під час запуску застосунку</string>
<string name="label_mergeWithLastMeasurement">Об\'єднати з останнім виміром</string>
<string name="label_bluetooth_searching">Пошук ваших Bluetooth вагів</string>
<string name="label_bluetooth_searching_finished">Пошук завершено</string>
<string name="label_enable_legend">Легенда графу</string>
<string name="label_enable_yaxis">Y-вісь</string>
<string name="label_enable_legend">Легенда графіку</string>
<string name="label_enable_yaxis">Вісь Y</string>
<string name="label_enable_labels">Мітка даних</string>
<string name="label_enable_points">Точка виміру</string>
<string name="label_delete_confirmation">Видалити підтвердження</string>
<string name="label_category_measurement_database">База вимірів</string>
<string name="label_maintainer">Супроводжувач</string>
<string name="label_website">Веб сайт</string>
<string name="label_website">Вебсайт</string>
<string name="label_license">Ліцензія</string>
<string name="label_automatic">авто</string>
<string name="label_theme">Тема</string>
@@ -145,12 +145,12 @@
<string name="label_feedback_message_positive">Гаразд</string>
<string name="label_feedback_message_negative">Ні, дякую</string>
<string name="error_max_scale_users">Досягнуто максимальну кількість користувачів ваг</string>
<string name="info_step_on_scale_for_reference">Будь ласка, станьте босоніж на ваги для зважування</string>
<string name="info_step_on_scale">Будь ласка, станьте босоніж на ваги</string>
<string name="info_step_on_scale_for_reference">Станьте на ваги босоніж для зважування</string>
<string name="info_step_on_scale">Станьте на ваги босоніж</string>
<string name="info_measuring">Виміряна вага: %.2f</string>
<string name="trisa_scale_not_paired">Ці ваги не було під\'єднано!
\n
\nУтримуйте кнопку на нижній стороні вагів, аби перевести їх в режим створення пари, тоді під\'єднайте знову для вводу паролю пристрою.</string>
\nУтримуйте кнопку на нижній стороні вагів, щоб перевести їх в режим створення пари, тоді під\'єднайте знову для введення паролю пристрою.</string>
<string name="trisa_scale_pairing_succeeded">Пару успішно створено!
\n
\nПовторно під\'єднайте пристрій, для отримання даних вимірювання.</string>
@@ -158,15 +158,15 @@
\n
\nБудь ласка, створіть нову тему, яка буде містити дані про помилку, на
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Перезапустити додаток</string>
<string name="customactivityoncrash_error_activity_close_app">Закрити додаток</string>
<string name="customactivityoncrash_error_activity_restart_app">Перезапустити застосунок</string>
<string name="customactivityoncrash_error_activity_close_app">Закрити застосунок</string>
<string name="customactivityoncrash_error_activity_error_details">Деталі помилки</string>
<string name="customactivityoncrash_error_activity_error_details_title">Деталі помилки</string>
<string name="customactivityoncrash_error_activity_error_details_close">Закрити</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Скопіювати до буферу обміну</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Скопійовано до буферу обміну</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Інформація про помилку</string>
<string name="toggle_expand">Розкрити деталі</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Скопіювати до буфера обміну</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Скопійовано до буфера обміну</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Відомості про помилку</string>
<string name="toggle_expand">Розгорнути деталі</string>
<string name="edit">Редагувати</string>
<string name="save">Зберегти</string>
<string name="label_day_view">За день</string>
@@ -174,11 +174,11 @@
<string name="label_weeks_view">За тиждень</string>
<string name="label_year_view">За рік</string>
<string name="permission_not_granted">Дозволу не отримано</string>
<string name="permission_bluetooth_info">Доступ до позиціювання необхідний для пошуку Bluetooth пристроїв. Дозвіл можна буде відмінити після того як пристрій буде знайдено.</string>
<string name="permission_location_service_info">Надайте доступ до позиціювання в налаштуваннях Android для пошуку Bluetooth пристроїв. Надалі цей дозвіл можна відхилити.</string>
<string name="permission_bluetooth_info_title">Інформація</string>
<string name="permission_read_write_data_description">Обробка openScale даних, з інформацією про користувача та всі збережені виміри</string>
<string name="permission_read_write_data_label">Обробка openScale даних</string>
<string name="permission_bluetooth_info">Доступ до позиціювання необхідний для пошуку Bluetooth пристроїв. Дозвіл можна буде відкликати після того, як пристрій буде знайдено.</string>
<string name="permission_location_service_info">Надайте доступ до позиціювання в налаштуваннях Android для пошуку Bluetooth пристроїв. Надалі цей дозвіл можна відкликати.</string>
<string name="permission_bluetooth_info_title">Відомості</string>
<string name="permission_read_write_data_description">Обробка openScale даних, з відомостями про користувача та всі збережені виміри</string>
<string name="permission_read_write_data_label">Обробка даних openScale</string>
<string name="label_next">Наступний</string>
<string name="label_long_press_drag_reorder">Натисніть, та утримуючи перемістіть вимір для зміни порядку</string>
<string name="label_set_default_order">Встановити типовий порядок</string>
@@ -191,10 +191,10 @@
<string name="theme_light">Світла</string>
<string name="theme_dark">Темна</string>
<string name="label_contribute_translation">Допомогти з перекладом</string>
<string name="label_add_or_fix_translation">Додати нову, чи справити наявну</string>
<string name="label_add_or_fix_translation">Додати нову чи виправити наявну</string>
<string name="label_percent">Відсоток</string>
<string name="label_estimated">За оцінкою</string>
<string name="label_estimate_measurement_summary">На основі ваги, зросту, віку, статі, і т.п.</string>
<string name="label_estimate_measurement_summary">На основі ваги, зросту, віку, статі тощо.</string>
<string name="label_auto_backup">Автоматичне резервування</string>
<string name="label_auto_backup_schedule">Розклад резервування</string>
<string name="label_overwrite_backup">Перезаписати попередню резервну копію</string>
@@ -202,7 +202,7 @@
<string name="label_weekly">щотижня</string>
<string name="label_monthly">щомісяця</string>
<string name="label_scale_not_supported">Ваші ваги не підтримуються\?</string>
<string name="label_click_to_help_add_support">Натисніть тут, аби допомогти нам додати підтримку для них</string>
<string name="label_click_to_help_add_support">Натисніть тут, щоб допомогти нам додати підтримку для них</string>
<string name="label_development">Розробка</string>
<string name="label_debug_log">Зберегти звіт зневадження до файлу</string>
<string name="label_your_bluetooth_scale">Ваші Bluetooth ваги</string>
@@ -212,7 +212,7 @@
<string name="label_visceral_fat">Вісцеральний жир</string>
<string name="label_chest">Окружність грудної клітини</string>
<string name="label_thigh">Окружність стегна</string>
<string name="label_biceps">Окружність біцепсу</string>
<string name="label_biceps">Окружність біцепса</string>
<string name="label_neck">Окружність шиї</string>
<string name="label_calories">Калорії</string>
<string name="label_fat_caliper">Вимірювач товщини жирової складки</string>
@@ -229,23 +229,24 @@
<string name="activity_level_moderate">Середній</string>
<string name="activity_level_heavy">Інтенсивний</string>
<string name="activity_level_extreme">Екстремальний</string>
<string name="label_upgrade_to_openScale_pro">Будь ласка, оновіться до openScale pro версії для підтримки Bluetooth</string>
<string name="label_upgrade_to_openScale_pro">Оновіться до openScale pro версії для підтримки Bluetooth</string>
<string name="action_donation">Підтримати</string>
<string name="info_on_date">на</string>
<string name="label_measurement_bar">Шкала вимірювань</string>
<string name="label_is_on_right_axis">На вісі праворуч</string>
<string name="info_scale_low_battery">Низький заряд батареї вагів (%d%%), будь-ласка зарядіть або замініть батареї</string>
<string name="info_bluetooth_connection_error_scale_offline">Не можу під\'єднатись до вагів, переконайтеся що вони увімкнені.</string>
<string name="info_scale_low_battery">Низький заряд батарей вагів (%d%%), зарядіть або замініть їх</string>
<string name="info_bluetooth_connection_error_scale_offline">Не вдалося під\'єднатись до вагів, переконайтеся що вони увімкнені.</string>
<string name="label_empty">брак даних</string>
<string name="label_age">Вік</string>
<string name="label_slide_welcome_top_text">Вітаємо в openScale</string>
<string name="label_slide_welcome_main_text">Програма з відкритим кодом для відстеження ваги та інших показників тіла за допомогою Bluetooth вагів.</string>
<string name="label_slide_welcome_top_text">Вітаємо в
\nopenScale</string>
<string name="label_slide_welcome_main_text">Застосунок з відкритим кодом для відстеження ваги та інших показників тіла за допомогою Bluetooth-вагів.</string>
<string name="label_slide_privacy_top_text">Захистіть свої персональні дані.</string>
<string name="label_slide_user_top_text">Підтримується декілька користувачів.</string>
<string name="label_slide_user_top_text">Підтримується кілька користувачів.</string>
<string name="label_slide_privacy_main_text">OpenScale не надсилає жодних даних до хмарного сховища та не має доступу до мережі.
\n
\nЯкщо потрібна синхронізація вашої ваги з GoogleFit чи за допомогою MQTT, можете встановити додаток: <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_support_top_text">Допоможіть покращити openScale.</string>
\nЯкщо потрібна синхронізація вашої ваги з GoogleFit чи за допомогою MQTT, можете встановити застосунок: <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_support_top_text">Допоможіть вдосконалити openScale.</string>
<string name="app_intro_skip_button">Пропустити</string>
<string name="app_intro_next_button">Далі</string>
<string name="app_intro_back_button">Назад</string>
@@ -253,25 +254,46 @@
<string name="label_slide_user_main_text">openScale не вимагає від вас створення онлайн облікового запису.
\n
\nПоказники тіла розраховані з вимірювань.</string>
<string name="label_slide_opensource_top_text">Це програма з відкритим кодом і високим ступенем прозорості.</string>
<string name="label_slide_opensource_top_text">Це застосунок з відкритим кодом і високим ступенем прозорості.</string>
<string name="label_slide_opensource_main_text">openScale ліцензується згідно з <a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>.
\n
\nВсі розрахунки показників виконуються прозоро, відсутні приховане передавання даних або ідентифікація користувача.
\n
\nВи можете знайти повний похідний код програми у <a href="https://github.com/oliexdev/openScale">GitHub</a>. Переконайтесь в надійності openScale переглянувши його.</string>
\nВи можете знайти весь джерельний код застосунку на <a href="https://github.com/oliexdev/openScale">GitHub</a>. Переконайтесь в надійності openScale переглянувши його.</string>
<string name="label_slide_bluetooth_top_text">Підтримуються ваги з Bluetooth від різноманітних виробників.</string>
<string name="label_slide_bluetooth_main_text">openScale підтримує багато моделей вагів з Bluetooth від різноманітних виробників.
\n
\nМи постійно розширюємо перелік моделей вагів які підтримуються. Ви можете знайти повний список і рівень підтримки для кожної моделі вагів у <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
\nМи постійно розширюємо перелік моделей вагів які підтримуються. Ви можете знайти вичерпний перелік і рівень підтримки для кожної моделі вагів на <a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">GitHub</a>.</string>
<string name="label_slide_metrics_top_text">Відслідковуйте і аналізуйте показники вашого тіла.</string>
<string name="label_slide_metrics_main_text">openScale підтримує понад 22 показники тіла.
\n
\nОберіть для відображення ті, які вас цікавлять.
\nОберіть для показу ті, які вас цікавлять.
\n
\n Якщо показники жиру, води та ваги м\'язів недоступні, їх можна облічити згідно з <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">науковими публікаціями</a>.</string>
<string name="label_slide_support_main_text">openScale потребує вашої допомоги. Створіть <a href="https://github.com/oliexdev/openScale/issues">нове issue</a>, якщо ви знайшли помилку, маєте ідею або питання, чи хочете додати підтримку вашої моделі вагів з Bluetooth.
\n Якщо показники жиру, води та ваги м\'язів недоступні, їх можна обчислити згідно з <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">науковими публікаціями</a>.</string>
<string name="label_slide_support_main_text">openScale потребує вашої допомоги. Створіть <a href="https://github.com/oliexdev/openScale/issues">нове issue</a>, якщо ви знайшли помилку, маєте ідею або питання, чи хочете додати підтримку вашої моделі ваг з Bluetooth.
\n
\nПідтримайте авторів цього проекту у подальшій розробці і постійному обслуговуванню, поставивши openScale позитивну оцінку у <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> або поставте зірочку у <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\nПідтримайте авторів цього проєкту у подальшій розробці й постійному обслуговуванні, поставивши openScale позитивну оцінку у <a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a> або поставте зірочку у <a href="https://github.com/oliexdev/openScale">GitHub</a>.
\n
\nМи високо цінимо ваші відгуки і підтримку. Дякуємо.</string>
\nМи високо цінимо ваші відгуки та підтримку. Дякуємо.</string>
<string name="info_assisted_weighing_old_reference_measurement">Увага: попереднє зважування-орієнтир давніше за добу</string>
<string name="amputation_level_foot">Стопа</string>
<string name="amputation_level_lower_leg_foot">Гомілка та стопа</string>
<string name="info_assisted_weighing_choose_reference_user">Виберіть користувача-орієнтира, вага якого відніматиметься</string>
<string name="info_assisted_weighing_no_reference_user">Відсутній користувач-орієнтир, створіть його</string>
<string name="info_assisted_weighing_no_reference_measurements">Користувач-орієнтир немає зважувань</string>
<string name="label_assisted_weighing">Зважування з порадами</string>
<string name="amputation_level_leg">Нога</string>
<string name="amputation_level_arm">Рука</string>
<string name="amputation_level_forearm_hand">Передпліччя та нога</string>
<string name="amputation_level_hand">Кисть</string>
<string name="amputation_level_none">Без ампутацій</string>
<string name="label_amputation_left">Ампутації ліворуч</string>
<string name="label_amputation_right">Ампутації праворуч</string>
<string name="label_amputation">Ампутації</string>
<string name="label_trend">Тренд</string>
<string name="label_prediction">Прогноз</string>
<string name="label_trend_line">Лінія тренду</string>
<string name="info_select_scale_user">Виберіть користувача ваг</string>
<string name="info_enter_consent_code_for_scale_user">Введіть код користувача ваг %s</string>
<string name="info_create_new_user_on_scale">Створити нового користувача ваг.</string>
</resources>

View File

@@ -1,5 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="title_overview">Tổng quan</string>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_overview">Tổng quan</string>
<string name="title_graph">Biểu đồ</string>
<string name="title_table">Bảng</string>
<string name="title_statistics">Thống kê</string>
@@ -7,12 +8,10 @@
<string name="title_measurements">Số đo</string>
<string name="title_about">Thông tin</string>
<string name="title_general">Tổng quan chung</string>
<string name="action_settings">Cài đặt</string>
<string name="action_bluetooth_status">Tình trạng Bluetooth</string>
<string name="open_drawer">mở</string>
<string name="close_drawer">đóng</string>
<string name="label_cancel">Hủy</string>
<string name="label_ok">OK</string>
<string name="label_yes">Đồng ý</string>
@@ -22,7 +21,6 @@
<string name="label_add_measurement">Thêm số đo</string>
<string name="label_share">Chia sẻ</string>
<string name="label_share_subject">Xuất file CSV (%s)</string>
<string name="label_weight">Trọng lượng</string>
<string name="label_bmi">Chỉ số khối cơ thể (BMI)</string>
<string name="label_bmr">Tỉ lệ trao đổi chất cơ bản BMR)</string>
@@ -37,16 +35,14 @@
<string name="label_whr">Tỉ số eo/hông</string>
<string name="label_bone">Trọng lượng xương</string>
<string name="label_smartUserAssign">Tự chọn người dùng</string>
<plurals name="label_days">
<item quantity="other">%d ngày</item>
</plurals>
<item quantity="other">%d ngày</item>
</plurals>
<string name="label_last_week">Trung bình tuần trước</string>
<string name="label_last_month">Trung bình tháng trước</string>
<string name="label_weight_difference">Trọng lượng thay đổi</string>
<string name="label_goal_date_is">Ngày đạt mục tiêu:</string>
<string name="label_days_left">Số ngày còn lại</string>
<string name="label_date">Ngày</string>
<string name="label_time">Giờ</string>
<string name="label_birthday">Ngày sinh</string>
@@ -58,15 +54,12 @@
<string name="label_female">Nữ</string>
<string name="label_goal_weight">Trọng lượng mục tiêu</string>
<string name="label_goal_date">Ngày đạt mục tiêu</string>
<string name="label_title_user">người sử dụng</string>
<string name="label_title_last_measurement">số đo lần trước</string>
<string name="label_title_goal">Mục tiêu</string>
<string name="label_import">Nhập</string>
<string name="label_export">Xuất</string>
<string name="label_delete_all">Xóa tất cả</string>
<string name="error_value_required">Giá trị là bắt buộc</string>
<string name="error_value_range">Giá trị vượt khoảng</string>
<string name="error_exporting">Lỗi khi xuất</string>
@@ -75,7 +68,6 @@
<string name="error_height_required">Lỗi: Chiều cao là bắt buộc</string>
<string name="error_initial_weight_required">Lỗi: Trọng lượng ban đầu là bắt buộc</string>
<string name="error_goal_weight_required">Lỗi: Trọng lượng mục tiêu là bắt buộc</string>
<string name="info_data_deleted">Bản ghi đã xóa</string>
<string name="info_data_all_deleted">Tất cả bản ghi đã xóa</string>
<string name="info_data_exported">Xuất đến</string>
@@ -96,44 +88,32 @@
<string name="info_bluetooth_connection_error">Lỗi Bluetooth không mong đợi</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] đến %4$s đã được thêm</string>
<string name="info_new_data_duplicated">số đo cùng ngày cùng giờ đã tồn tại</string>
<string name="info_enter_user_name">Tên của bạn</string>
<string name="info_no_selected_user">Không có người dùng nào. Hãy tạo một người dùng trong phần cài đặt.</string>
<string name="info_no_evaluation_available">Không tính được giá trị</string>
<string name="question_really_delete">Xóa bản ghi\?</string>
<string name="question_really_delete_all">Xóa tất cả bản ghi của tất cả người dùng\?</string>
<string name="question_really_delete_user">Xóa người dùng\?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Kết nối với cân khi khởi động</string>
<string name="label_mergeWithLastMeasurement">Hợp nhất với số đo lần trước</string>
<string name="label_bluetooth_searching">Đang tìm cân Bluetooth của bạn</string>
<string name="label_bluetooth_searching_finished">Tìm xong</string>
<string name="label_enable_labels">Nhãn cho dữ liệu</string>
<string name="label_enable_points">Điểm cho dữ liệu</string>
<string name="label_delete_confirmation">Hỏi trước khi xóa</string>
<string name="label_category_measurement_database">Cơ sở dữ liệu các số đo</string>
<string name="label_maintainer">Người bảo trì</string>
<string name="label_website">Trang web</string>
<string name="label_license">Thỏa thuận sử dụng</string>
<string name="label_automatic">tự động</string>
<string name="label_theme">Theme</string>
<string name="label_reminder">Nhắc nhở</string>
<string name="label_reminder_weekdays">Ngày</string>
<string name="label_reminder_time">Giờ</string>
<string name="label_reminder_notify_text">Văn bản thông báo</string>
<string name="default_value_reminder_notify_text">Đến giờ cân</string>
<string name="info_on_date">vào ngày</string>
<string name="Monday">Thứ Hai</string>
<string name="Tuesday">Thứ Ba</string>
<string name="Wednesday">Thứ Tư</string>
@@ -151,7 +131,6 @@
<string name="label_initial_weight">Trọng lượng ban đầu</string>
<string name="label_goal_line">Đường thẳng mục tiêu</string>
<string name="label_help">Trợ giúp</string>
<string name="label_feedback_message_enjoying">Thích openScale\?</string>
<string name="label_feedback_message_rate_app">Vậy đánh giá trên Google Play hoặc Github nhé\?</string>
<string name="label_feedback_message_issue">Bạn có muốn phản hồi gì không\?</string>
@@ -159,7 +138,6 @@
<string name="label_feedback_message_no">Không hẳn</string>
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">Không, cảm ơn</string>
<string name="error_max_scale_users">Đã đạt đến số người sử đụng cùng lúc tối đa</string>
<string name="info_step_on_scale_for_reference">Vui lòng bước chân trần lên cân để lấy số đo tham chiếu</string>
<string name="info_measuring">Đang đo trọng lượng: %.2f</string>
@@ -169,12 +147,10 @@
<string name="trisa_scale_pairing_succeeded">Ghép đôi thành công!
\n
\nKết nối lại để lấy số đo.</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Lỗi không mong đợi vừa xuất hiện.
\n
\nVui lòng tạo một vấn đề mới bao gồm thông tin chi tiết về lỗi tại
\nhttps://github.com/oliexdev/openScale/issues
\n</string>
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Khởi động lại ứng dụng</string>
<string name="customactivityoncrash_error_activity_close_app">Đóng ứng dụng</string>
<string name="customactivityoncrash_error_activity_error_details">Chi tiết lỗi</string>
@@ -186,10 +162,8 @@
<string name="toggle_expand">Bật tắt mở rộng</string>
<string name="edit">Chỉnh sửa</string>
<string name="save">Lưu</string>
<string name="label_month_view">Xem theo tháng</string>
<string name="label_weeks_view">Xem theo tuần</string>
<string name="permission_not_granted">Quyền không được cấp</string>
<string name="permission_bluetooth_info">Cần quyền truy cập vị trí thô để tìm kiếm các thiết bị Bluetooth. Nó có thể được thu hồi sau khi thiết bị được tìm thấy.</string>
<string name="permission_bluetooth_info_title">Thông tin</string>
@@ -244,4 +218,4 @@
<string name="activity_level_moderate">Vận động trung bình</string>
<string name="activity_level_heavy">Vận động nặng</string>
<string name="activity_level_extreme">Vận động cực nặng</string>
</resources>
</resources>

View File

@@ -2,4 +2,299 @@
<resources>
<string name="title_graph">图标</string>
<string name="title_overview">概览</string>
<string name="label_measurement_bar">测量杆</string>
<string name="label_bluetooth_searching">正在搜索你的蓝牙磅秤</string>
<string name="label_amputation_right">右截肢</string>
<string name="label_amputation_left">左截肢</string>
<string name="label_assisted_weighing">辅助称重</string>
<string name="app_intro_done_button">完成</string>
<string name="app_intro_back_button">返回</string>
<string name="app_intro_next_button">下一步</string>
<string name="app_intro_skip_button">跳过</string>
<string name="label_slide_support_main_text">openScale需要你。如果发现bug有想法、问题或希望帮助支持蓝牙磅秤请创建一个<a href="https://github.com/oliexdev/openScale/issues">新issue</a>.
\n
\n通过在<a href="http://play.google.com/store/apps/details?id=com.health.openscale.pro">GooglePlay</a>上给予一个积极评价或在<a href="https://github.com/oliexdev/openScale">Github</a>上标星激励这个项目的创建者进行进一步的开发和持续的维护。
\n
\n非常感谢您的支持和积极反馈。谢谢你。</string>
<string name="label_slide_support_top_text">帮助改进openScale。</string>
<string name="label_slide_metrics_main_text">应用支持超过22个身体指标。
\n
\n对它进行配置以显示您所关心的指标。
\n
\n如果身体脂肪、水分和去脂体重缺失可以根据 <a href="https://github.com/oliexdev/openScale/wiki/Body-metric-estimations">科学出版物</a>进行估算。</string>
<string name="label_slide_metrics_top_text">跟踪并分析你的身体指标。</string>
<string name="label_slide_bluetooth_main_text">openScale内置了对来自不同制造商的许多蓝牙秤的支持。
\n
\n我们不断改进和扩大支持的规模。您可以在<a href="https://github.com/oliexdev/openScale/wiki/Supported-scales-in-openScale">Github</a>上找到完整的列表以及每种磅秤的支持级别。</string>
<string name="label_slide_bluetooth_top_text">支持来自不同制造商的各种蓝牙秤。</string>
<string name="label_slide_opensource_main_text">openScale 在<a href="https://github.com/oliexdev/openScale/blob/master/LICENSE">GPLv3</a>许可下授权。
\n
\n所有的身体计算是透明的没有隐藏的数据传输功能或进行用户识别。
\n
\n你可在<a href="https://github.com/oliexdev/openScale">Github</a>上找到完整源码。通过检查代码来取信于己。</string>
<string name="label_slide_opensource_top_text">它是一个高透明度的开源软件。</string>
<string name="label_slide_user_main_text">openScale不需要你创建一个在线账户。
\n
\n身体指标是根据测量值计算出来的。</string>
<string name="label_slide_user_top_text">支持多用户。</string>
<string name="label_slide_privacy_main_text">openScale 不向云发送任何数据,且没有访问互联网的权限是这一承诺的有力保证。
\n
\n如果你真的想将你的体重同步到GoogleFit或与MQTT同步您可以安装 <a href="https://play.google.com/store/apps/details?id=com.health.openscale.sync">openScale sync</a>.</string>
<string name="label_slide_privacy_top_text">保护你的个人健康数据。</string>
<string name="label_slide_welcome_main_text">带蓝牙秤支持的开源体重和身体指标跟踪软件。</string>
<string name="label_slide_welcome_top_text">欢迎来到
\nopenScale</string>
<string name="label_upgrade_to_openScale_pro">要获取蓝牙支持功能请升级到openScale pro</string>
<string name="amputation_level_leg"></string>
<string name="amputation_level_lower_leg_foot">小腿和足</string>
<string name="amputation_level_foot"></string>
<string name="amputation_level_arm">手臂</string>
<string name="amputation_level_forearm_hand">前臂和腿</string>
<string name="amputation_level_hand"></string>
<string name="amputation_level_none">无截肢</string>
<string name="activity_level_extreme">极限运动</string>
<string name="activity_level_heavy">重度运动</string>
<string name="activity_level_moderate">中等运动</string>
<string name="activity_level_mild">轻微运动</string>
<string name="activity_level_sedentary">久坐</string>
<string name="label_activity_level">活动水平</string>
<string name="label_measure_unit">测量单位</string>
<string name="label_caliper3_female">臀部皮肤褶</string>
<string name="label_caliper2_female">腹部皮肤褶</string>
<string name="label_caliper1_female">三头肌皮肤褶</string>
<string name="label_caliper3_male">大腿皮肤褶</string>
<string name="label_caliper2_male">腹部皮肤褶</string>
<string name="label_caliper1_male">胸部皮肤褶</string>
<string name="label_fat_caliper">身体脂肪卡钳</string>
<string name="label_calories">卡路里</string>
<string name="label_neck">颈围</string>
<string name="label_biceps">肱二头肌围</string>
<string name="label_thigh">大腿围</string>
<string name="label_chest">胸围</string>
<string name="label_visceral_fat">内脏脂肪</string>
<string name="label_configure_widget">配置小部件</string>
<string name="label_select_user">选择用户</string>
<string name="label_select_measurement">挑选测量值</string>
<string name="label_your_bluetooth_scale">你的蓝牙秤</string>
<string name="label_debug_log">保存调试日志到文件</string>
<string name="label_development">开发</string>
<string name="label_click_to_help_add_support">单击这里帮助添加对它的支持</string>
<string name="label_scale_not_supported">你的磅秤不受支持吗?</string>
<string name="label_monthly">每月</string>
<string name="label_weekly">每周</string>
<string name="label_daily">每天</string>
<string name="label_overwrite_backup">覆盖先前的备份</string>
<string name="label_auto_backup_schedule">备份时间表</string>
<string name="label_auto_backup">自动备份</string>
<string name="label_estimate_measurement_summary">基于体重、身高、年龄、性别等。</string>
<string name="label_estimated">预估的</string>
<string name="label_percent">百分比</string>
<string name="label_add_or_fix_translation">添加新翻译或修复现有翻译中的错误</string>
<string name="label_contribute_translation">贡献翻译</string>
<string name="theme_dark">深色</string>
<string name="theme_light">浅色</string>
<string name="label_language">语言</string>
<string name="language_default">系统默认</string>
<string name="label_estimation_formula">估算方程式</string>
<string name="label_measurement_in_percent">百分比为单位的测量值</string>
<string name="label_is_on_right_axis">在右轴上</string>
<string name="label_export_overwrite">覆盖先前的导出 \"%s\"\?</string>
<string name="label_set_default_order">设置默认顺序</string>
<string name="label_long_press_drag_reorder">长按和拖动测量数据以重新排序</string>
<string name="label_next">下一步</string>
<string name="permission_read_write_data_label">读取/写入openScale数据</string>
<string name="permission_read_write_data_description">读取/写入openScale数据包括用户信息和所有保存的测量数据</string>
<string name="permission_bluetooth_info_title">信息</string>
<string name="permission_location_service_info">在Android设置中授予搜索蓝牙设备的位置访问权限。然后可选择撤销它。</string>
<string name="permission_bluetooth_info">搜索蓝牙设备需要位置许可。在找到设备后可以撤销它。</string>
<string name="permission_not_granted">未授予权限</string>
<string name="label_rolling_chart">旋转图表</string>
<string name="label_year_view">年视图</string>
<string name="label_weeks_view">周视图</string>
<string name="label_month_view">月视图</string>
<string name="label_day_view">天视图</string>
<string name="save">保存</string>
<string name="edit">编辑</string>
<string name="toggle_expand">展开/并拢</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">错误信息</string>
<string name="customactivityoncrash_error_activity_error_details_copied">已复制到剪贴板</string>
<string name="customactivityoncrash_error_activity_error_details_copy">复制到剪贴板</string>
<string name="customactivityoncrash_error_activity_error_details_close">关闭</string>
<string name="customactivityoncrash_error_activity_error_details_title">错误详情</string>
<string name="customactivityoncrash_error_activity_error_details">错误详情</string>
<string name="customactivityoncrash_error_activity_close_app">关闭应用</string>
<string name="customactivityoncrash_error_activity_restart_app">重启应用</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">发生一个意外错误。
\n
\n请在https://github.com/oliexdev/openScale/issues
\n创建一个包括错误详情的新issue</string>
<string name="trisa_scale_pairing_succeeded">配对成功!
\n
\n重新连接以获取测量数据。</string>
<string name="trisa_scale_not_paired">这台磅秤还未配对!
\n
\n按住磅秤底部的按钮切换到配对模式然后重新连接获取设备密码。</string>
<string name="info_measuring">正在量体重: %.2f</string>
<string name="info_step_on_scale">请赤脚站在磅秤上</string>
<string name="info_step_on_scale_for_reference">请赤脚踏上磅秤,以作参考测量</string>
<string name="error_max_scale_users">已达scale 并存用户的最大数目</string>
<string name="label_feedback_message_negative">不,谢谢</string>
<string name="label_feedback_message_positive"></string>
<string name="label_feedback_message_no">不太想</string>
<string name="label_feedback_message_yes"></string>
<string name="label_feedback_message_issue">你愿意提供一些反馈吗\?</string>
<string name="label_feedback_message_rate_app">在谷歌Play或GitHub上评分怎么样\?</string>
<string name="label_feedback_message_enjoying">享受 openScale\?</string>
<string name="label_help">帮助</string>
<string name="label_goal_line">目标线</string>
<string name="label_regression_line_degree">回归多项式程度</string>
<string name="label_regression_line">回归线</string>
<string name="label_initial_weight">初始体重</string>
<string name="label_ignoreOutOfRange">忽略超范围数据</string>
<string name="label_not_found">未找到</string>
<string name="label_export_dir">导出目录</string>
<string name="label_backup">备份</string>
<string name="label_importBackup">导入备份</string>
<string name="label_exportBackup">导出备份</string>
<string name="label_bt_device_no_support">该设备不受支持</string>
<string name="Sunday">周日</string>
<string name="Saturday">周六</string>
<string name="Friday">周五</string>
<string name="Thursday">周四</string>
<string name="Wednesday">周三</string>
<string name="Tuesday">周二</string>
<string name="Monday">周一</string>
<string name="info_on_date"></string>
<string name="default_value_reminder_notify_text">称体重时间</string>
<string name="label_reminder_notify_text">通知文本</string>
<string name="label_reminder_time">时间</string>
<string name="label_reminder_weekdays">天数</string>
<string name="label_reminder">提示</string>
<string name="label_theme">主题</string>
<string name="label_automatic">自动</string>
<string name="label_license">许可证</string>
<string name="label_website">网站</string>
<string name="label_maintainer">维护者</string>
<string name="label_category_measurement_database">测量数据库</string>
<string name="label_delete_confirmation">删除确认</string>
<string name="label_enable_points">数据点</string>
<string name="label_enable_labels">数据标签</string>
<string name="label_enable_yaxis">Y轴</string>
<string name="label_enable_legend">图例</string>
<string name="label_bluetooth_searching_finished">搜索完成</string>
<string name="label_mergeWithLastMeasurement">与上一个测量值合并</string>
<string name="label_bluetooth_enable">开机时连接到scale</string>
<string name="label_bluetooth_title">蓝牙</string>
<string name="question_really_delete_user">删除用户\?</string>
<string name="question_really_delete_all">删除所有用户的所有条目\?</string>
<string name="question_really_delete">删除条目\?</string>
<string name="info_no_evaluation_available">无法评估该数值</string>
<string name="info_no_selected_user">没有用户存在。请在设置中创建一个。</string>
<string name="info_enter_user_name">你的名字</string>
<string name="info_new_data_duplicated">已存在日期和时间相同的测量值</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] 已添加 to %4$s</string>
<string name="info_bluetooth_connection_disconnected">蓝牙连接已关闭</string>
<string name="info_bluetooth_connection_error_scale_offline">无法连接到scale请确保其处于开启状态。</string>
<string name="info_bluetooth_connection_error">意外的蓝牙错误</string>
<string name="info_bluetooth_init">初始化蓝牙设备</string>
<string name="info_bluetooth_connection_successful">已连接</string>
<string name="info_bluetooth_no_device_set">选择一个蓝牙设备</string>
<string name="info_bluetooth_no_device_retrying">无法建立连接,正在重试…</string>
<string name="info_bluetooth_no_device">未发现蓝牙设备</string>
<string name="info_bluetooth_connection_lost">蓝牙连接丢失</string>
<string name="info_bluetooth_try_connection">正在连接到:</string>
<string name="info_scale_low_battery">电池电量不足(%d%%),请重新充电或更换秤电池</string>
<string name="info_assisted_weighing_choose_reference_user">选择体重被下调的参考用户</string>
<string name="info_assisted_weighing_no_reference_user">参考用户不可用,请创建一个</string>
<string name="info_assisted_weighing_old_reference_measurement">警告:最后基准测量于一天多前进行</string>
<string name="info_assisted_weighing_no_reference_measurements">参考用户没有测量值</string>
<string name="info_is_not_available">不可用</string>
<string name="info_is_not_enable">已禁用</string>
<string name="info_is_enable">已启用</string>
<string name="info_is_not_visible">不可见</string>
<string name="info_is_visible">可见</string>
<string name="info_enter_comment">可选注释</string>
<string name="info_enter_value_in">数值单位</string>
<string name="info_data_imported">导入来源</string>
<string name="info_data_exported">导出到</string>
<string name="info_data_all_deleted">所有条目已删除</string>
<string name="info_data_deleted">条目已删除</string>
<string name="error_goal_weight_required">目标体重必需</string>
<string name="error_initial_weight_required">初始体重必需</string>
<string name="error_height_required">身高必需</string>
<string name="error_user_name_required">名称必需</string>
<string name="error_importing">无法导入</string>
<string name="error_exporting">无法导出</string>
<string name="error_value_range">数值超出区间</string>
<string name="error_value_required">数值必需</string>
<string name="label_delete_all">删除全部</string>
<string name="label_export">导出</string>
<string name="label_import">导入</string>
<string name="label_title_goal">目标</string>
<string name="label_title_last_measurement">上次测量值</string>
<string name="label_title_user">用户</string>
<string name="label_goal_date">目标日期</string>
<string name="label_goal_weight">目标体重</string>
<string name="label_female">女性</string>
<string name="label_male">男性</string>
<string name="label_amputation">截肢</string>
<string name="label_gender">性别</string>
<string name="label_scale_unit">标度单位</string>
<string name="label_height">身高</string>
<string name="label_user_name">名字</string>
<string name="label_birthday">生日</string>
<string name="label_time">时间</string>
<string name="label_date">日期</string>
<string name="label_days_left">剩余天数</string>
<string name="label_goal_date_is">目标日期:</string>
<string name="label_weight_difference">体重差异</string>
<string name="label_last_month">上月平均</string>
<string name="label_last_week">上周平均数</string>
<plurals name="label_days">
<item quantity="other">第 %d 天</item>
</plurals>
<string name="label_smartUserAssign">智能用户分配</string>
<string name="label_age">年龄</string>
<string name="label_bone">骨量</string>
<string name="label_whr">腰臀比</string>
<string name="label_whtr">腰围身高比</string>
<string name="label_comment">评论</string>
<string name="label_hip">臀围</string>
<string name="label_waist">腰围</string>
<string name="label_lbm">去脂体重</string>
<string name="label_muscle">肌肉</string>
<string name="label_water">全身含水量</string>
<string name="label_fat">体脂肪</string>
<string name="label_tdee">每日总能源消耗 (TDEE)</string>
<string name="label_bmr">基础代谢率 (BMR)</string>
<string name="label_bmi">身体质量指数 (BMI)</string>
<string name="label_weight">重量</string>
<string name="label_share_subject">openScale 数据导出(%s)</string>
<string name="label_share">分享</string>
<string name="label_add_measurement">添加量度</string>
<string name="label_add_user">添加用户</string>
<string name="label_empty">空白</string>
<string name="label_delete">删除</string>
<string name="label_no"></string>
<string name="label_yes"></string>
<string name="label_ok"></string>
<string name="label_cancel">取消</string>
<string name="close_drawer">关闭</string>
<string name="open_drawer">打开</string>
<string name="action_donation">捐赠</string>
<string name="action_bluetooth_status">蓝牙状态</string>
<string name="action_settings">设置</string>
<string name="title_general">通用</string>
<string name="title_about">关于</string>
<string name="title_measurements">测量值</string>
<string name="title_users">用户</string>
<string name="title_statistics">统计数据</string>
<string name="title_table">表格</string>
<string name="label_trend">趋势</string>
<string name="label_prediction">预测</string>
<string name="label_trend_line">趋势线</string>
<string name="label_estimate_measurement">估算的测量值</string>
<string name="info_select_scale_user">选择 scale 用户</string>
<string name="info_enter_consent_code_for_scale_user">输入 scale 用户 %s 的 PIN/同意码</string>
<string name="info_create_new_user_on_scale">创建 scale 新用户。</string>
</resources>

View File

@@ -85,7 +85,7 @@
<string name="info_bluetooth_connection_successful">連結成功</string>
<string name="info_bluetooth_init">啟動藍芽設備</string>
<string name="info_bluetooth_connection_error">藍芽出現錯誤</string>
<string name="info_new_data_added">"%1$.2f%2$s [%3$s] 加入至 %4$s "</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] 加入至 %4$s</string>
<string name="info_new_data_duplicated">相同日期及時間的測量數值已經存在</string>
<string name="info_enter_user_name">你的名字</string>
<string name="info_no_selected_user">沒有此用戶。 請在設定中加入新用戶。</string>
@@ -158,8 +158,7 @@
<string name="label_month_view">按月顯示</string>
<string name="label_weeks_view">按周顯示</string>
<string name="permission_not_granted">未有開放權限</string>
<string name="permission_bluetooth_info">搜尋藍芽器材時, 需要開放大概位置的權限。
\n當搜尋完成時 此權限可以再次關上。</string>
<string name="permission_bluetooth_info">搜尋藍芽器材時, 需要開放大概位置的權限。當搜尋完成時, 此權限可以再次關上。</string>
<string name="permission_bluetooth_info_title">資料</string>
<string name="label_next">下一項</string>
<string name="label_set_default_order">設定預設的排序</string>
@@ -219,4 +218,14 @@
<string name="label_fat_caliper">體脂夾</string>
<string name="action_donation">捐赠</string>
<string name="label_empty"></string>
<string name="info_assisted_weighing_no_reference_measurements">參考用戶沒有測量值</string>
<string name="info_assisted_weighing_old_reference_measurement">警告:上次參考測量時間超過一天</string>
<string name="info_assisted_weighing_choose_reference_user">選擇減去重量的參考使用者</string>
<string name="info_assisted_weighing_no_reference_user">沒有可用的參考使用者,請創建一個</string>
<string name="label_amputation_left">左截肢</string>
<string name="label_amputation_right">右截肢</string>
<string name="label_assisted_weighing">輔助稱重</string>
<string name="label_amputation">截肢</string>
<string name="label_age">年齡</string>
<string name="info_create_new_user_on_scale">在秤上建立新用戶。</string>
</resources>

View File

@@ -5,22 +5,28 @@
<!-- Keep the list below alphabetically sorted -->
<!-- Native names from https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes -->
<item>Arabic (العربية)</item>
<item>Bengali (বাংলা)</item>
<item>Catalan (català)</item>
<item>Chinese (traditional; 中文 (繁體))</item>
<item>Chinese (simplified); 中文 (汉语))</item>
<item>Croatian (hrvatski jezik)</item>
<item>Czech (čeština)</item>
<item>Danish (dansk)</item>
<item>Dutch (Nederlands)</item>
<item>English</item>
<item>Esperanto</item>
<item>Finnish (suomi)</item>
<item>French (français)</item>
<item>Galician (Galego)</item>
<item>German (Deutsch)</item>
<item>Greek (ελληνικά)</item>
<item>Hebrew (עברית)</item>
<item>Hungarian (magyar)</item>
<item>Indonesian (Bahasa Indonesia)</item>
<item>Italian (Italiano)</item>
<item>Japanese (日本語)</item>
<item>Korean (한국어)</item>
<item>Lithuanian (lietuvių kalba)</item>
<item>Norwegian Bokmål (Norsk)</item>
<item>Polish (język polski)</item>
<item>Portuguese (Brazil; Português)</item>
@@ -30,7 +36,9 @@
<item>Slovenian (Slovenski Jezik)</item>
<item>Spanish (Español)</item>
<item>Swedish (Svenska)</item>
<item>Tamil (தமிழ்)</item>
<item>Turkish (Türkçe)</item>
<item>Ukrainian (Українська)</item>
<item>Vietnamese (Tiếng Việt)</item>
</string-array>
@@ -38,22 +46,28 @@
<item>default</item>
<!-- The order below should match the language order above -->
<item>ar</item>
<item>bn-rBD</item>
<item>ca</item>
<item>zh-TW</item>
<item>zh-CN</item>
<item>hr</item>
<item>cs</item>
<item>da</item>
<item>nl</item>
<item>en</item>
<item>eo</item>
<item>fi</item>
<item>fr</item>
<item>gl</item>
<item>de</item>
<item>el</item>
<item>iw</item>
<item>hu</item>
<item>id</item>
<item>it</item>
<item>ja</item>
<item>ko</item>
<item>lt</item>
<item>nb</item>
<item>pl</item>
<item>pt-BR</item>
@@ -63,7 +77,9 @@
<item>sl</item>
<item>es</item>
<item>sv</item>
<item>ta</item>
<item>tr</item>
<item>uk</item>
<item>vi</item>
</string-array>

View File

@@ -23,7 +23,7 @@
<string name="label_add_user">Add user</string>
<string name="label_add_measurement">Add measurement</string>
<string name="label_share">Share</string>
<string name="label_share_subject">CSV data export (%s)</string>
<string name="label_share_subject">openScale data export (%s)</string>
<string name="label_weight">Weight</string>
<string name="label_bmi">Body mass index (BMI)</string>
<string name="label_bmr">Basal metabolic rate (BMR)</string>
@@ -90,7 +90,7 @@
<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_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>
@@ -104,7 +104,7 @@
<string name="info_bluetooth_connection_error">Unexpected Bluetooth error</string>
<string name="info_bluetooth_connection_error_scale_offline">Could not connect to scale, please ensure it is on.</string>
<string name="info_bluetooth_connection_disconnected">Bluetooth connection closed</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] to %4$s added</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] added to %4$s</string>
<string name="info_new_data_duplicated">measurement with the same date and time already exist</string>
<string name="info_enter_user_name">Your name</string>
<string name="info_no_selected_user">No user exists. Please create one in the settings.</string>
@@ -164,6 +164,8 @@
<string name="error_max_scale_users">Max. number of concurrent scale users reached</string>
<string name="info_step_on_scale_for_reference">Please step barefoot on the scale for reference measurements</string>
<string name="info_step_on_scale">Please step barefoot on the scale</string>
<string name="info_select_scale_user">Select scale user</string>
<string name="info_enter_consent_code_for_scale_user">Enter PIN/consent code for scale user %s</string>
<string name="info_measuring">Measuring weight: %.2f</string>
<string name="trisa_scale_not_paired">This scale has not been paired!\n\nHold the button on the bottom of the scale to switch it to pairing mode, and then reconnect to retrieve the device password.</string>
<string name="trisa_scale_pairing_succeeded">Pairing succeeded!\n\nReconnect to retrieve measurement data.</string>
@@ -284,4 +286,5 @@
<string name="app_intro_next_button">Next</string>
<string name="app_intro_back_button">Back</string>
<string name="app_intro_done_button">Done</string>
<string name="info_create_new_user_on_scale">Create new user on scale.</string>
</resources>

View File

@@ -2,12 +2,11 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3"
}
}
@@ -15,7 +14,6 @@ allprojects {
repositories {
maven { url 'https://maven.google.com' }
maven { url 'https://jitpack.io' }
jcenter()
mavenCentral()
}
}

View File

@@ -1,6 +1,6 @@
#Mon Jun 01 15:44:49 CEST 2020
#Thu Nov 05 13:59:58 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip