1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-31 03:59:56 +02:00
This commit is contained in:
oliexdev
2018-07-18 20:03:53 +02:00
34 changed files with 1362 additions and 617 deletions

View File

@@ -42,6 +42,7 @@ dependencies {
implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:support-v4:${supportLibVersion}"
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
// HelloCharts
implementation 'com.github.lecho:hellocharts-library:1.5.8@aar'

View File

@@ -21,6 +21,7 @@ import com.health.openscale.BuildConfig;
import timber.log.Timber;
public class Application extends android.app.Application {
OpenScale openScale;
private class TimberLogAdapter extends Timber.DebugTree {
@Override
@@ -40,5 +41,8 @@ public class Application extends android.app.Application {
// Create OpenScale instance
OpenScale.createInstance(getApplicationContext());
// Hold on to the instance for as long as the application exists
openScale = OpenScale.getInstance();
}
}

View File

@@ -28,8 +28,9 @@ import com.health.openscale.core.OpenScale;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import timber.log.Timber;
@@ -95,7 +96,8 @@ public class AlarmBackupHandler
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (!prefs.getBoolean("overwriteBackup", false)) {
databaseName = DateFormat.getDateInstance(DateFormat.SHORT).format(new Date()) + "_" + databaseName;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
databaseName = dateFormat.format(new Date()) + "_" + databaseName;
}
File exportFile = new File(exportDir, databaseName);

View File

@@ -30,6 +30,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.content.ContextCompat;
import com.health.openscale.core.datatypes.ScaleMeasurement;
@@ -49,9 +50,12 @@ public abstract class BluetoothCommunication {
public enum BT_MACHINE_STATE {BT_INIT_STATE, BT_CMD_STATE, BT_CLEANUP_STATE}
private static final long LE_SCAN_TIMEOUT_MS = 10 * 1000;
protected Context context;
private Handler callbackBtHandler;
private Handler handler;
private BluetoothGatt bluetoothGatt;
private boolean connectionEstablished;
private BluetoothGattCallback gattCallback;
@@ -81,6 +85,7 @@ public abstract class BluetoothCommunication {
public BluetoothCommunication(Context context)
{
this.context = context;
handler = new Handler();
btAdapter = BluetoothAdapter.getDefaultAdapter();
gattCallback = new GattCallback();
bluetoothGatt = null;
@@ -96,8 +101,8 @@ public abstract class BluetoothCommunication {
return bluetoothGatt.getServices();
}
protected boolean discoverDeviceBeforeConnecting() {
return false;
protected boolean hasBluetoothGattService(UUID service) {
return bluetoothGatt != null && bluetoothGatt.getService(service) != null;
}
/**
@@ -125,7 +130,10 @@ public abstract class BluetoothCommunication {
* @param infoText the information text that is displayed to the status code.
*/
protected void setBtStatus(BT_STATUS_CODE statusCode, String infoText) {
callbackBtHandler.obtainMessage(statusCode.ordinal(), infoText).sendToTarget();
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
statusCode.ordinal(), infoText).sendToTarget();
}
}
/**
@@ -134,7 +142,10 @@ public abstract class BluetoothCommunication {
* @param scaleMeasurement the scale data that should be added to openScale
*/
protected void addScaleData(ScaleMeasurement scaleMeasurement) {
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleMeasurement).sendToTarget();
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS_CODE.BT_RETRIEVE_SCALE_DATA.ordinal(), scaleMeasurement).sendToTarget();
}
}
/**
@@ -144,7 +155,10 @@ public abstract class BluetoothCommunication {
* @param value the value to be used
*/
protected void sendMessage(int msg, Object value) {
callbackBtHandler.obtainMessage(BT_STATUS_CODE.BT_SCALE_MESSAGE.ordinal(), msg, 0, value).sendToTarget();
if (callbackBtHandler != null) {
callbackBtHandler.obtainMessage(
BT_STATUS_CODE.BT_SCALE_MESSAGE.ordinal(), msg, 0, value).sendToTarget();
}
}
/**
@@ -261,6 +275,7 @@ public abstract class BluetoothCommunication {
BluetoothGattCharacteristic gattCharacteristic = bluetoothGatt.getService(service)
.getCharacteristic(characteristic);
Timber.d("Read characteristic %s", characteristic);
bluetoothGatt.readCharacteristic(gattCharacteristic);
}
@@ -268,6 +283,7 @@ public abstract class BluetoothCommunication {
BluetoothGattDescriptor gattDescriptor = bluetoothGatt.getService(service)
.getCharacteristic(characteristic).getDescriptor(descriptor);
Timber.d("Read descriptor %s", descriptor);
bluetoothGatt.readDescriptor(gattDescriptor);
}
@@ -280,16 +296,21 @@ public abstract class BluetoothCommunication {
protected void setIndicationOn(UUID service, UUID characteristic, UUID descriptor) {
Timber.d("Set indication on for %s", characteristic);
BluetoothGattCharacteristic gattCharacteristic =
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
try {
BluetoothGattCharacteristic gattCharacteristic =
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
handleRequests();
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
handleRequests();
}
}
catch (Exception e) {
Timber.e(e);
}
}
@@ -302,16 +323,21 @@ public abstract class BluetoothCommunication {
protected void setNotificationOn(UUID service, UUID characteristic, UUID descriptor) {
Timber.d("Set notification on for %s", characteristic);
BluetoothGattCharacteristic gattCharacteristic =
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
try {
BluetoothGattCharacteristic gattCharacteristic =
bluetoothGatt.getService(service).getCharacteristic(characteristic);
bluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
handleRequests();
synchronized (lock) {
descriptorRequestQueue.add(
new GattObjectValue<>(
gattCharacteristic.getDescriptor(descriptor),
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
handleRequests();
}
}
catch (Exception e) {
Timber.e(e);
}
}
@@ -349,12 +375,16 @@ public abstract class BluetoothCommunication {
return "";
}
if (data.length == 0) {
return "";
}
final StringBuilder stringBuilder = new StringBuilder(3 * data.length);
for (byte byteChar : data) {
stringBuilder.append(String.format("%02X ", byteChar));
}
return stringBuilder.toString();
return stringBuilder.substring(0, stringBuilder.length() - 1);
}
protected byte xorChecksum(byte[] data, int offset, int length) {
@@ -384,9 +414,33 @@ public abstract class BluetoothCommunication {
*
* @param hwAddress the Bluetooth address to connect to
*/
public void connect(final String hwAddress) {
Timber.i("Connecting to [%s] (driver: %s)", hwAddress, driverName());
public void connect(String hwAddress) {
logBluetoothStatus();
// Some good tips to improve BLE connections:
// https://android.jlelse.eu/lessons-for-first-time-android-bluetooth-le-developers-i-learned-the-hard-way-fee07646624
btAdapter.cancelDiscovery();
stopLeScan();
// Don't do any cleanup if disconnected before fully connected
btMachineState = BT_MACHINE_STATE.BT_CLEANUP_STATE;
// Running an LE scan during connect improves connectivity on some phones
// (e.g. Sony Xperia Z5 compact, Android 7.1.1). For some scales (e.g. Medisana BS444)
// it seems to be a requirement that the scale is discovered before connecting to it.
// Otherwise the connection almost never succeeds.
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
startLeScanForDevice(hwAddress);
}
else {
Timber.d("No coarse location permission, connecting without LE scan");
connectGatt(hwAddress);
}
}
private void logBluetoothStatus() {
Timber.d("BT is%s enabled, state=%d, scan mode=%d, is%s discovering",
btAdapter.isEnabled() ? "" : " not", btAdapter.getState(),
btAdapter.getScanMode(), btAdapter.isDiscovering() ? "" : " not");
@@ -401,47 +455,11 @@ public abstract class BluetoothCommunication {
Timber.d("Connected GATT_SERVER device: %s [%s]",
device.getName(), device.getAddress());
}
// Some good tips to improve BLE connections:
// https://android.jlelse.eu/lessons-for-first-time-android-bluetooth-le-developers-i-learned-the-hard-way-fee07646624
final boolean doDiscoveryFirst = discoverDeviceBeforeConnecting();
// Running an LE scan during connect improves connectivity on some phones
// (e.g. Sony Xperia Z5 compact, Android 7.1.1).
btAdapter.cancelDiscovery();
if (leScanCallback == null) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
Timber.d("Starting LE scan");
leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
Timber.d("Found LE device %s [%s]", device.getName(), device.getAddress());
if (!doDiscoveryFirst || !device.getAddress().equals(hwAddress)) {
return;
}
synchronized (lock) {
connectGatt(device);
}
}
};
btAdapter.startLeScan(leScanCallback);
}
else {
Timber.d("No coarse location permission, skipping LE scan");
}
}
// Don't do any cleanup if disconnected before fully connected
btMachineState = BT_MACHINE_STATE.BT_CLEANUP_STATE;
if (!doDiscoveryFirst || leScanCallback == null) {
connectGatt(btAdapter.getRemoteDevice(hwAddress));
}
}
private void connectGatt(BluetoothDevice device) {
Timber.i("Connecting to [%s] (driver: %s)", device.getAddress(), driverName());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
bluetoothGatt = device.connectGatt(
context, false, gattCallback, BluetoothDevice.TRANSPORT_LE);
@@ -451,15 +469,55 @@ public abstract class BluetoothCommunication {
}
}
private void connectGatt(String hwAddress) {
connectGatt(btAdapter.getRemoteDevice(hwAddress));
}
private void startLeScanForDevice(final String hwAddress) {
leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
Timber.d("Found LE device %s [%s]", device.getName(), device.getAddress());
if (!device.getAddress().equals(hwAddress)) {
return;
}
synchronized (lock) {
stopLeScan();
connectGatt(device);
}
}
};
Timber.d("Starting LE scan for device [%s]", hwAddress);
btAdapter.startLeScan(leScanCallback);
handler.postAtTime(new Runnable() {
@Override
public void run() {
Timber.d("Device not found in LE scan, connecting directly");
synchronized (lock) {
stopLeScan();
connectGatt(hwAddress);
}
}
}, leScanCallback, SystemClock.uptimeMillis() + LE_SCAN_TIMEOUT_MS);
}
private void stopLeScan() {
if (leScanCallback != null) {
Timber.d("Stopping LE scan");
btAdapter.stopLeScan(leScanCallback);
handler.removeCallbacksAndMessages(leScanCallback);
leScanCallback = null;
}
}
/**
* Disconnect from a Bluetooth device
*/
public void disconnect(boolean doCleanup) {
synchronized (lock) {
if (leScanCallback != null) {
btAdapter.stopLeScan(leScanCallback);
leScanCallback = null;
}
stopLeScan();
if (bluetoothGatt == null) {
return;
@@ -467,15 +525,32 @@ public abstract class BluetoothCommunication {
Timber.i("Disconnecting%s", doCleanup ? " (with cleanup)" : "");
handler.removeCallbacksAndMessages(null);
callbackBtHandler = null;
if (doCleanup) {
if (btMachineState != BT_MACHINE_STATE.BT_CLEANUP_STATE) {
setBtMachineState(BT_MACHINE_STATE.BT_CLEANUP_STATE);
nextMachineStateStep();
}
handler.post(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (openRequest) {
handler.postDelayed(this, 10);
} else {
bluetoothGatt.close();
bluetoothGatt = null;
}
}
}
});
}
else {
bluetoothGatt.close();
bluetoothGatt = null;
}
bluetoothGatt.close();
bluetoothGatt = null;
}
}
@@ -509,6 +584,8 @@ public abstract class BluetoothCommunication {
synchronized (lock) {
// check for pending request
if (openRequest) {
Timber.d("Request pending (queue %d %d)",
descriptorRequestQueue.size(), characteristicRequestQueue.size());
return; // yes, do nothing
}
@@ -517,8 +594,9 @@ public abstract class BluetoothCommunication {
if (descriptor != null) {
descriptor.gattObject.setValue(descriptor.value);
Timber.d("Write descriptor %s: %s",
descriptor.gattObject.getUuid(), byteInHex(descriptor.gattObject.getValue()));
Timber.d("Write descriptor %s: %s (queue: %d %d)",
descriptor.gattObject.getUuid(), byteInHex(descriptor.gattObject.getValue()),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeDescriptor(descriptor.gattObject)) {
Timber.e("Failed to initiate write of descriptor %s",
descriptor.gattObject.getUuid());
@@ -532,8 +610,9 @@ public abstract class BluetoothCommunication {
if (characteristic != null) {
characteristic.gattObject.setValue(characteristic.value);
Timber.d("Write characteristic %s: %s",
characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()));
Timber.d("Write characteristic %s: %s (queue: %d %d)",
characteristic.gattObject.getUuid(), byteInHex(characteristic.gattObject.getValue()),
descriptorRequestQueue.size(), characteristicRequestQueue.size());
if (!bluetoothGatt.writeCharacteristic(characteristic.gattObject)) {
Timber.e("Failed to initiate write of characteristic %s",
characteristic.gattObject.getUuid());
@@ -557,10 +636,7 @@ public abstract class BluetoothCommunication {
if (newState == BluetoothProfile.STATE_CONNECTED) {
synchronized (lock) {
if (leScanCallback != null) {
btAdapter.stopLeScan(leScanCallback);
leScanCallback = null;
}
stopLeScan();
}
connectionEstablished = true;
@@ -588,7 +664,8 @@ public abstract class BluetoothCommunication {
@Override
public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
Timber.d("onServicesDiscovered: status=%d", status);
Timber.d("onServicesDiscovered: status=%d (%d services)",
status, gatt.getServices().size());
synchronized (lock) {
cmdStepNr = 0;
@@ -615,24 +692,32 @@ public abstract class BluetoothCommunication {
setBtMachineState(BT_MACHINE_STATE.BT_INIT_STATE);
}
private void postDelayedHandleRequests() {
// Wait a short while before starting the next operation as suggested
// on the android.jlelse.eu link above.
handler.postDelayed(new Runnable() {
@Override
public void run() {
synchronized (lock) {
openRequest = false;
handleRequests();
}
}
}, 60);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status) {
synchronized (lock) {
openRequest = false;
handleRequests();
}
postDelayedHandleRequests();
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
synchronized (lock) {
openRequest = false;
handleRequests();
}
postDelayedHandleRequests();
}
@Override
@@ -644,8 +729,7 @@ public abstract class BluetoothCommunication {
synchronized (lock) {
onBluetoothDataRead(gatt, characteristic, status);
openRequest = false;
handleRequests();
postDelayedHandleRequests();
}
}
@@ -667,10 +751,7 @@ public abstract class BluetoothCommunication {
Timber.d("onDescriptorRead %s (status=%d): %s",
descriptor.getUuid(), status, byteInHex(descriptor.getValue()));
synchronized (lock) {
openRequest = false;
handleRequests();
}
postDelayedHandleRequests();
}
}
}

View File

@@ -15,28 +15,26 @@
*/
package com.health.openscale.core.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import com.health.openscale.core.datatypes.ScaleMeasurement;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.UUID;
import timber.log.Timber;
public class BluetoothCustomOpenScale extends BluetoothCommunication {
private final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // Standard SerialPortService ID
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"); // Bluetooth Modul HM-10
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private BluetoothSocket btSocket = null;
private BluetoothDevice btDevice = null;
private BluetoothConnectedThread btConnectThread = null;
private String string_data = new String();
public BluetoothCustomOpenScale(Context context) {
super(context);
@@ -44,12 +42,33 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
@Override
public String driverName() {
return "Custom Open Scale";
return "Custom openScale";
}
@Override
protected boolean nextInitCmd(int stateNr) {
return false;
switch (stateNr) {
case 0:
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, WEIGHT_MEASUREMENT_CONFIG);
break;
case 1:
Calendar cal = Calendar.getInstance();
String date_time = String.format(Locale.US, "2%1d,%1d,%1d,%1d,%1d,%1d,",
cal.get(Calendar.YEAR)-2000,
cal.get(Calendar.MONTH) + 1,
cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
cal.get(Calendar.SECOND));
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, date_time.getBytes());
break;
default:
return false;
}
return true;
}
@Override
@@ -62,211 +81,94 @@ public class BluetoothCustomOpenScale extends BluetoothCommunication {
return false;
}
@Override
public void connect(String hwAddress) {
if (btAdapter == null) {
setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
return;
}
btDevice = btAdapter.getRemoteDevice(hwAddress);
try {
// Get a BluetoothSocket to connect with the given BluetoothDevice
btSocket = btDevice.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get a bluetooth socket");
btDevice = null;
return;
}
Thread socketThread = new Thread() {
@Override
public void run() {
try {
if (!btSocket.isConnected()) {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
btSocket.connect();
// Bluetooth connection was successful
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_ESTABLISHED);
btConnectThread = new BluetoothConnectedThread();
btConnectThread.start();
}
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
disconnect(false);
setBtStatus(BT_STATUS_CODE.BT_NO_DEVICE_FOUND);
}
}
};
socketThread.start();
}
@Override
public void disconnect(boolean doCleanup) {
if (btSocket != null) {
if (btSocket.isConnected()) {
try {
btSocket.close();
btSocket = null;
} catch (IOException closeException) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't close bluetooth socket");
}
}
}
if (btConnectThread != null) {
btConnectThread.cancel();
btConnectThread = null;
}
btDevice = null;
}
public void clearEEPROM()
{
sendBtData("9");
byte[] cmd = {(byte)'9'};
writeBytes(WEIGHT_MEASUREMENT_SERVICE, WEIGHT_MEASUREMENT_CHARACTERISTIC, cmd);
}
private boolean sendBtData(String data) {
if (btSocket.isConnected()) {
btConnectThread = new BluetoothConnectedThread();
btConnectThread.write(data.getBytes());
@Override
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
final byte[] data = gattCharacteristic.getValue();
btConnectThread.cancel();
return true;
}
if (data != null) {
for (byte character : data) {
string_data += (char) (character & 0xFF);
return false;
}
private class BluetoothConnectedThread extends Thread {
private InputStream btInStream;
private OutputStream btOutStream;
private volatile boolean isCancel;
public BluetoothConnectedThread() {
isCancel = false;
// Get the input and output bluetooth streams
try {
btInStream = btSocket.getInputStream();
btOutStream = btSocket.getOutputStream();
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Can't get bluetooth input or output stream " + e.getMessage());
}
}
public void run() {
final StringBuilder btLine = new StringBuilder();
// Keep listening to the InputStream until an exception occurs (e.g. device partner goes offline)
while (!isCancel) {
try {
// stream read is a blocking method
char btChar = (char) btInStream.read();
btLine.append(btChar);
if (btLine.charAt(btLine.length() - 1) == '\n') {
ScaleMeasurement scaleMeasurement = parseBtString(btLine.toString());
if (scaleMeasurement != null) {
addScaleData(scaleMeasurement);
}
btLine.setLength(0);
}
} catch (IOException e) {
cancel();
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
if (character == '\n') {
parseBtString(string_data);
string_data = new String();
}
}
}
private ScaleMeasurement parseBtString(String btString) throws IOException {
ScaleMeasurement scaleBtData = new ScaleMeasurement();
btString = btString.substring(0, btString.length() - 1); // delete newline '\n' of the string
if (btString.charAt(0) != '$' && btString.charAt(2) != '$') {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Parse error of bluetooth string. String has not a valid format");
}
String btMsg = btString.substring(3, btString.length()); // message string
switch (btString.charAt(1)) {
case 'I':
Timber.i("MCU Information: %s", btMsg);
break;
case 'E':
Timber.e("MCU Error: %s", btMsg);
break;
case 'S':
Timber.i("MCU stored data size: %s", btMsg);
break;
case 'D':
String[] csvField = btMsg.split(",");
try {
int checksum = 0;
checksum ^= Integer.parseInt(csvField[0]);
checksum ^= Integer.parseInt(csvField[1]);
checksum ^= Integer.parseInt(csvField[2]);
checksum ^= Integer.parseInt(csvField[3]);
checksum ^= Integer.parseInt(csvField[4]);
checksum ^= Integer.parseInt(csvField[5]);
checksum ^= (int) Float.parseFloat(csvField[6]);
checksum ^= (int) Float.parseFloat(csvField[7]);
checksum ^= (int) Float.parseFloat(csvField[8]);
checksum ^= (int) Float.parseFloat(csvField[9]);
int btChecksum = Integer.parseInt(csvField[10]);
if (checksum == btChecksum) {
scaleBtData.setId(-1);
scaleBtData.setUserId(Integer.parseInt(csvField[0]));
String date_string = csvField[1] + "/" + csvField[2] + "/" + csvField[3] + "/" + csvField[4] + "/" + csvField[5];
scaleBtData.setDateTime(new SimpleDateFormat("yyyy/MM/dd/HH/mm").parse(date_string));
scaleBtData.setWeight(Float.parseFloat(csvField[6]));
scaleBtData.setFat(Float.parseFloat(csvField[7]));
scaleBtData.setWater(Float.parseFloat(csvField[8]));
scaleBtData.setMuscle(Float.parseFloat(csvField[9]));
return scaleBtData;
} else {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error calculated checksum (" + checksum + ") and received checksum (" + btChecksum + ") is different");
}
} catch (ParseException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
} catch (NumberFormatException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding a number of bluetooth string (" + e.getMessage() + ")");
}
break;
default:
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error unknown MCU command");
}
return null;
}
public void write(byte[] bytes) {
try {
btOutStream.write(bytes);
} catch (IOException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while writing to bluetooth socket " + e.getMessage());
}
}
public void cancel() {
isCancel = true;
}
}
private void parseBtString(String btString) {
btString = btString.substring(0, btString.length() - 1); // delete newline '\n' of the string
if (btString.charAt(0) != '$' && btString.charAt(2) != '$') {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Parse error of bluetooth string. String has not a valid format: " + btString);
}
String btMsg = btString.substring(3, btString.length()); // message string
switch (btString.charAt(1)) {
case 'I':
Timber.d("MCU Information: %s", btMsg);
break;
case 'E':
Timber.e("MCU Error: %s", btMsg);
break;
case 'S':
Timber.d("MCU stored data size: %s", btMsg);
break;
case 'F':
Timber.d("All data sent");
clearEEPROM();
disconnect(false);
break;
case 'D':
String[] csvField = btMsg.split(",");
try {
int checksum = 0;
checksum ^= Integer.parseInt(csvField[0]);
checksum ^= Integer.parseInt(csvField[1]);
checksum ^= Integer.parseInt(csvField[2]);
checksum ^= Integer.parseInt(csvField[3]);
checksum ^= Integer.parseInt(csvField[4]);
checksum ^= Integer.parseInt(csvField[5]);
checksum ^= (int) Float.parseFloat(csvField[6]);
checksum ^= (int) Float.parseFloat(csvField[7]);
checksum ^= (int) Float.parseFloat(csvField[8]);
checksum ^= (int) Float.parseFloat(csvField[9]);
int btChecksum = Integer.parseInt(csvField[10]);
if (checksum == btChecksum) {
ScaleMeasurement scaleBtData = new ScaleMeasurement();
String date_string = csvField[1] + "/" + csvField[2] + "/" + csvField[3] + "/" + csvField[4] + "/" + csvField[5];
scaleBtData.setDateTime(new SimpleDateFormat("yyyy/MM/dd/HH/mm").parse(date_string));
scaleBtData.setWeight(Float.parseFloat(csvField[6]));
scaleBtData.setFat(Float.parseFloat(csvField[7]));
scaleBtData.setWater(Float.parseFloat(csvField[8]));
scaleBtData.setMuscle(Float.parseFloat(csvField[9]));
addScaleData(scaleBtData);
} else {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error calculated checksum (" + checksum + ") and received checksum (" + btChecksum + ") is different");
}
} catch (ParseException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding bluetooth date string (" + e.getMessage() + ")");
} catch (NumberFormatException e) {
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error while decoding a number of bluetooth string (" + e.getMessage() + ")");
}
break;
default:
setBtStatus(BT_STATUS_CODE.BT_UNEXPECTED_ERROR, "Error unknown MCU command : " + btString);
}
}
}

View File

@@ -156,8 +156,8 @@ public class BluetoothDebug extends BluetoothCommunication {
logService(service, false);
}
disconnect(false);
setBtStatus(BT_STATUS_CODE.BT_CONNECTION_LOST);
disconnect(false);
return false;
}

View File

@@ -29,7 +29,7 @@ import com.health.openscale.core.utils.Converters;
import java.util.Arrays;
import java.util.UUID;
public class BluetoothExcelvanCF369BLE extends BluetoothCommunication {
public class BluetoothExcelvanCF36xBLE extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000FFF0-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000FFF1-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_CUSTOM0_CHARACTERISTIC = UUID.fromString("0000FFF4-0000-1000-8000-00805f9b34fb");
@@ -37,13 +37,13 @@ public class BluetoothExcelvanCF369BLE extends BluetoothCommunication {
private byte[] receivedData = new byte[]{};
public BluetoothExcelvanCF369BLE(Context context) {
public BluetoothExcelvanCF36xBLE(Context context) {
super(context);
}
@Override
public String driverName() {
return "Excelvan CF369BLE";
return "Excelvan CF36xBLE";
}
@Override
@@ -117,8 +117,10 @@ public class BluetoothExcelvanCF369BLE extends BluetoothCommunication {
if (data != null && data.length > 0) {
// if data is body scale type
if (data.length == 16 && data[0] == (byte)0xcf) {
// if data is body scale type. At least some variants (e.g. CF366BLE) of this scale
// return a 17th byte representing "physiological age". Allow (but ignore) that byte
// to support those variants.
if ((data.length >= 16 && data.length <= 17) && data[0] == (byte)0xcf) {
if (!Arrays.equals(data, receivedData)) { // accepts only one data of the same content
receivedData = data;
parseBytes(data);
@@ -135,6 +137,7 @@ public class BluetoothExcelvanCF369BLE extends BluetoothCommunication {
float visceralFat = weightBytes[11] & 0xFF;
float water = Converters.fromUnsignedInt16Be(weightBytes, 12) / 10.0f;
float bmr = Converters.fromUnsignedInt16Be(weightBytes, 14);
// weightBytes[16] is an (optional, ignored) "physiological age" in some scale variants.
ScaleMeasurement scaleBtData = new ScaleMeasurement();

View File

@@ -109,7 +109,7 @@ public class BluetoothExingtechY1 extends BluetoothCommunication {
final ScaleUser selectedUser = OpenScale.getInstance().getSelectedScaleUser();
scaleBtData.setWeight(Converters.toKilogram(weight, selectedUser.getScaleUnit()));
scaleBtData.setWeight(weight);
scaleBtData.setFat(fat);
scaleBtData.setMuscle(muscle);
scaleBtData.setWater(water);

View File

@@ -38,14 +38,14 @@ public class BluetoothFactory {
if (name.startsWith("BEURER BF710".toLowerCase(Locale.US))) {
return new BluetoothBeurerSanitas(context, BluetoothBeurerSanitas.DeviceType.BEURER_BF710);
}
if (name.equals("openScale_MCU".toLowerCase(Locale.US))) {
if (name.equals("openScale".toLowerCase(Locale.US))) {
return new BluetoothCustomOpenScale(context);
}
if (name.equals("Mengii".toLowerCase(Locale.US))) {
return new BluetoothDigooDGSO38H(context);
}
if (name.equals("Electronic Scale".toLowerCase(Locale.US))) {
return new BluetoothExcelvanCF369BLE(context);
return new BluetoothExcelvanCF36xBLE(context);
}
if (name.equals("VScale".toLowerCase(Locale.US))) {
return new BluetoothExingtechY1(context);
@@ -58,7 +58,7 @@ public class BluetoothFactory {
}
// BS444 || BS440
if (deviceName.startsWith("013197") || deviceName.startsWith("0202B6")) {
return new BluetoothMedisanaBS444(context);
return new BluetoothMedisanaBS44x(context);
}
if (deviceName.startsWith("SWAN") || name.equals("icomon".toLowerCase(Locale.US))) {
return new BluetoothMGB(context);

View File

@@ -25,7 +25,7 @@ import com.health.openscale.core.utils.Converters;
import java.util.Date;
import java.util.UUID;
public class BluetoothMedisanaBS444 extends BluetoothCommunication {
public class BluetoothMedisanaBS44x extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("000078b2-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a21-0000-1000-8000-00805f9b34fb"); // indication, read-only
private final UUID FEATURE_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00008a22-0000-1000-8000-00805f9b34fb"); // indication, read-only
@@ -39,16 +39,11 @@ public class BluetoothMedisanaBS444 extends BluetoothCommunication {
// Scale time is in seconds since 2010-01-01
private static final long SCALE_UNIX_TIMESTAMP_OFFSET = 1262304000;
public BluetoothMedisanaBS444(Context context) {
public BluetoothMedisanaBS44x(Context context) {
super(context);
btScaleMeasurement = new ScaleMeasurement();
}
@Override
protected boolean discoverDeviceBeforeConnecting() {
return true;
}
@Override
public String driverName() {
return "Medisana BS44x";

View File

@@ -31,12 +31,16 @@ import timber.log.Timber;
public class BluetoothOneByone extends BluetoothCommunication {
private final UUID WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION = UUID.fromString("0000181B-0000-1000-8000-00805f9b34fb");
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION = UUID.fromString("00002A9C-0000-1000-8000-00805f9b34fb"); // read, indication
private final UUID WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT = UUID.fromString("0000fff4-0000-1000-8000-00805f9b34fb"); // notify
private final UUID WEIGHT_MEASUREMENT_SERVICE = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
private final UUID CMD_MEASUREMENT_CHARACTERISTIC = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb"); // write only
private final UUID WEIGHT_MEASUREMENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private float lastWeight;
public BluetoothOneByone(Context context) {
super(context);
}
@@ -50,7 +54,18 @@ public class BluetoothOneByone extends BluetoothCommunication {
protected boolean nextInitCmd(int stateNr) {
switch (stateNr) {
case 0:
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION, WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION, WEIGHT_MEASUREMENT_CONFIG);
lastWeight = 0;
if (hasBluetoothGattService(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION)) {
setIndicationOn(WEIGHT_MEASUREMENT_SERVICE_BODY_COMPOSITION,
WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION,
WEIGHT_MEASUREMENT_CONFIG);
}
else {
setNotificationOn(WEIGHT_MEASUREMENT_SERVICE,
WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT,
WEIGHT_MEASUREMENT_CONFIG);
}
break;
case 1:
ScaleUser currentUser = OpenScale.getInstance().getSelectedScaleUser();
@@ -91,11 +106,20 @@ public class BluetoothOneByone extends BluetoothCommunication {
@Override
public void onBluetoothDataChange(BluetoothGatt bluetoothGatt, BluetoothGattCharacteristic gattCharacteristic) {
final byte[] data = gattCharacteristic.getValue();
if (data == null) {
return;
}
final UUID uuid = gattCharacteristic.getUuid();
// if data is valid data
if (data != null && data.length == 20) {
if (data.length == 20
&& uuid.equals(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION)) {
parseBytes(data);
}
else if (data.length == 11
&& uuid.equals(WEIGHT_MEASUREMENT_CHARACTERISTIC_BODY_COMPOSITION_ALT)) {
parseBytesAlt(data);
}
}
private void parseBytes(byte[] weightBytes) {
@@ -104,10 +128,31 @@ public class BluetoothOneByone extends BluetoothCommunication {
Timber.d("weight: %.2f, impedance: %d", weight, impedance);
ScaleMeasurement scaleBtData = new ScaleMeasurement();
// This check should be a bit more elaborate, but it works for now...
if (weight != lastWeight) {
lastWeight = weight;
scaleBtData.setWeight(weight);
ScaleMeasurement scaleBtData = new ScaleMeasurement();
scaleBtData.setWeight(weight);
addScaleData(scaleBtData);
addScaleData(scaleBtData);
}
}
private void parseBytesAlt(byte[] weightBytes) {
float weight = Converters.fromUnsignedInt16Le(weightBytes, 3) / 100.0f;
boolean done = (weightBytes[9] & 0xff) == 0;
Timber.d("weight: %.2f%s", weight, done ? " (done)" : "");
// This check should be a bit more elaborate, but it works for now...
if (done && weight != lastWeight) {
lastWeight = weight;
ScaleMeasurement scaleBtData = new ScaleMeasurement();
scaleBtData.setWeight(weight);
addScaleData(scaleBtData);
}
}
}

View File

@@ -127,7 +127,7 @@ public class BluetoothYunmaiSE_Mini extends BluetoothCommunication {
scaleBtData.setDateTime(new Date(timestamp));
float weight = Converters.fromUnsignedInt16Be(weightBytes, 13) / 100.0f;
scaleBtData.setWeight(Converters.toKilogram(weight, selectedUser.getScaleUnit()));
scaleBtData.setWeight(weight);
if (isMini) {
float fat = Converters.fromUnsignedInt16Be(weightBytes, 17) / 100.0f;

View File

@@ -353,7 +353,12 @@ public class ScaleMeasurement implements Cloneable {
}
public void setComment(String comment) {
this.comment = comment;
if (comment == null) {
this.comment = "";
}
else {
this.comment = comment;
}
}
public float getBMI(float body_height) {

View File

@@ -41,6 +41,8 @@ import android.widget.TextView;
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.health.openscale.core.utils.PolynomialFitter;
import com.health.openscale.gui.activities.DataEntryActivity;
import com.health.openscale.gui.views.BMRMeasurementView;
@@ -412,7 +414,8 @@ public class GraphFragment extends Fragment implements FragmentUpdateListener {
if (prefs.getBoolean("goalLine", true)) {
Stack<PointValue> valuesGoalLine = new Stack<>();
float goalWeight = openScale.getSelectedScaleUser().getGoalWeight();
final ScaleUser user = openScale.getSelectedScaleUser();
float goalWeight = Converters.fromKilogram(user.getGoalWeight(), user.getScaleUnit());
valuesGoalLine.push(new PointValue(0, goalWeight));
valuesGoalLine.push(new PointValue(maxDays, goalWeight));

View File

@@ -17,24 +17,18 @@ package com.health.openscale.gui.fragments;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
@@ -45,45 +39,43 @@ import com.health.openscale.gui.activities.DataEntryActivity;
import com.health.openscale.gui.views.MeasurementView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
public class TableFragment extends Fragment implements FragmentUpdateListener {
private View tableView;
private ListView tableDataView;
private LinearLayout tableHeaderView;
private LinearLayout subpageView;
private RecyclerView recyclerView;
private MeasurementsAdapter adapter;
private LinearLayoutManager layoutManager;
private List<MeasurementView> measurementViews;
private int selectedSubpageNr;
private static final String SELECTED_SUBPAGE_NR_KEY = "selectedSubpageNr";
public TableFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
tableView = inflater.inflate(R.layout.fragment_table, container, false);
subpageView = tableView.findViewById(R.id.subpageView);
tableDataView = tableView.findViewById(R.id.tableDataView);
tableHeaderView = tableView.findViewById(R.id.tableHeaderView);
recyclerView = tableView.findViewById(R.id.tableDataView);
tableDataView.setAdapter(new ListViewAdapter());
tableDataView.setOnItemClickListener(new onClickListenerRow());
recyclerView.setHasFixedSize(true);
if (savedInstanceState == null) {
selectedSubpageNr = 0;
}
else {
selectedSubpageNr = savedInstanceState.getInt(SELECTED_SUBPAGE_NR_KEY);
}
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(
recyclerView.getContext(), layoutManager.getOrientation()));
adapter = new MeasurementsAdapter();
recyclerView.setAdapter(adapter);
measurementViews = MeasurementView.getMeasurementList(
getContext(), MeasurementView.DateTimeOrder.FIRST);
@@ -103,236 +95,176 @@ public class TableFragment extends Fragment implements FragmentUpdateListener {
super.onDestroyView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SELECTED_SUBPAGE_NR_KEY, selectedSubpageNr);
}
@Override
public void updateOnView(List<ScaleMeasurement> scaleMeasurementList)
{
final int maxSize = 25;
final int subpageCount = (int)Math.ceil(scaleMeasurementList.size() / (double)maxSize);
if (selectedSubpageNr >= subpageCount) {
selectedSubpageNr = Math.max(0, subpageCount - 1);
}
subpageView.removeAllViews();
Button moveSubpageLeft = new Button(tableView.getContext());
moveSubpageLeft.setText("<");
moveSubpageLeft.setPadding(0,0,0,0);
moveSubpageLeft.setTextColor(Color.WHITE);
moveSubpageLeft.setBackground(ContextCompat.getDrawable(tableView.getContext(), R.drawable.flat_selector));
moveSubpageLeft.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
moveSubpageLeft.getLayoutParams().height = pxImageDp(20);
moveSubpageLeft.getLayoutParams().width = pxImageDp(50);
moveSubpageLeft.setOnClickListener(new onClickListenerMoveSubpageLeft());
moveSubpageLeft.setEnabled(selectedSubpageNr > 0);
subpageView.addView(moveSubpageLeft);
for (int i = 0; i < subpageCount; i++) {
TextView subpageNrView = new TextView(tableView.getContext());
subpageNrView.setOnClickListener(new onClickListenerSubpageSelect());
subpageNrView.setText(Integer.toString(i+1));
subpageNrView.setTextColor(Color.GRAY);
subpageNrView.setPadding(10, 10, 20, 10);
subpageView.addView(subpageNrView);
}
if (subpageView.getChildCount() > 1) {
TextView selectedSubpageNrView = (TextView) subpageView.getChildAt(selectedSubpageNr + 1);
if (selectedSubpageNrView != null) {
selectedSubpageNrView.setTypeface(null, Typeface.BOLD);
selectedSubpageNrView.setTextColor(Color.WHITE);
}
}
Button moveSubpageRight = new Button(tableView.getContext());
moveSubpageRight.setText(">");
moveSubpageRight.setPadding(0,0,0,0);
moveSubpageRight.setTextColor(Color.WHITE);
moveSubpageRight.setBackground(ContextCompat.getDrawable(tableView.getContext(), R.drawable.flat_selector));
moveSubpageRight.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
moveSubpageRight.getLayoutParams().height = pxImageDp(20);
moveSubpageRight.getLayoutParams().width = pxImageDp(50);
moveSubpageRight.setOnClickListener(new onClickListenerMoveSubpageRight());
moveSubpageRight.setEnabled(selectedSubpageNr + 1 < subpageCount);
subpageView.addView(moveSubpageRight);
subpageView.setVisibility(subpageCount > 1 ? View.VISIBLE : View.GONE);
tableHeaderView.removeAllViews();
final int iconHeight = pxImageDp(20);
ArrayList<MeasurementView> visibleMeasurements = new ArrayList<>();
for (MeasurementView measurement : measurementViews) {
if (measurement.isVisible()) {
ImageView headerIcon = new ImageView(tableView.getContext());
headerIcon.setImageDrawable(measurement.getIcon());
headerIcon.setLayoutParams(new TableRow.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.MATCH_PARENT, 1));
headerIcon.getLayoutParams().width = 0;
headerIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
headerIcon.getLayoutParams().height = pxImageDp(20);
tableHeaderView.addView(headerIcon);
visibleMeasurements.add(measurement);
if (!measurement.isVisible()) {
continue;
}
ImageView headerIcon = new ImageView(tableView.getContext());
headerIcon.setImageDrawable(measurement.getIcon());
headerIcon.setLayoutParams(new TableRow.LayoutParams(0, iconHeight, 1));
headerIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
tableHeaderView.addView(headerIcon);
visibleMeasurements.add(measurement);
}
ListViewAdapter adapter = (ListViewAdapter) tableDataView.getAdapter();
final int startOffset = maxSize * selectedSubpageNr;
final int endOffset = Math.min(startOffset + maxSize + 1, scaleMeasurementList.size());
adapter.setMeasurements(visibleMeasurements, scaleMeasurementList.subList(startOffset, endOffset), maxSize);
adapter.setMeasurements(visibleMeasurements, scaleMeasurementList);
}
private int pxImageDp(float dp) {
return (int)(dp * getResources().getDisplayMetrics().density + 0.5f);
}
private class onClickListenerRow implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(tableView.getContext(), DataEntryActivity.class);
intent.putExtra(DataEntryActivity.EXTRA_ID, (int)id);
startActivity(intent);
}
}
private class MeasurementsAdapter extends RecyclerView.Adapter<MeasurementsAdapter.ViewHolder> {
public static final int VIEW_TYPE_MEASUREMENT = 0;
public static final int VIEW_TYPE_YEAR = 1;
private class onClickListenerMoveSubpageLeft implements View.OnClickListener {
@Override
public void onClick(View v) {
if (selectedSubpageNr > 0) {
selectedSubpageNr--;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
public class ViewHolder extends RecyclerView.ViewHolder {
public LinearLayout measurementView;
public ViewHolder(LinearLayout view) {
super(view);
measurementView = view;
}
}
}
private class onClickListenerMoveSubpageRight implements View.OnClickListener {
@Override
public void onClick(View v) {
if (selectedSubpageNr < (subpageView.getChildCount() - 3)) {
selectedSubpageNr++;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
}
}
}
private class onClickListenerSubpageSelect implements View.OnClickListener {
@Override
public void onClick(View v) {
TextView nrView = (TextView)v;
selectedSubpageNr = Integer.parseInt(nrView.getText().toString())-1;
updateOnView(OpenScale.getInstance().getScaleMeasurementList());
}
}
private class ListViewAdapter extends BaseAdapter {
private List<MeasurementView> visibleMeasurements;
private List<ScaleMeasurement> scaleMeasurements;
private int measurementsToShow = 0;
private Spanned[][] stringCache;
public void setMeasurements(List<MeasurementView> visibleMeasurements,
List<ScaleMeasurement> scaleMeasurements,
int maxSize) {
List<ScaleMeasurement> scaleMeasurements) {
this.visibleMeasurements = visibleMeasurements;
this.scaleMeasurements = scaleMeasurements;
measurementsToShow = Math.min(scaleMeasurements.size(), maxSize);
this.scaleMeasurements = new ArrayList<>(scaleMeasurements.size() + 10);
stringCache = new Spanned[measurementsToShow][visibleMeasurements.size()];
Calendar calendar = Calendar.getInstance();
if (!scaleMeasurements.isEmpty()) {
calendar.setTime(scaleMeasurements.get(0).getDateTime());
}
calendar.set(calendar.get(Calendar.YEAR), 0, 1, 0, 0, 0);
calendar.set(calendar.MILLISECOND, 0);
// Copy all measurements from input parameter to member variable and insert
// an extra "null" entry when the year changes.
Date yearStart = calendar.getTime();
for (int i = 0; i < scaleMeasurements.size(); ++i) {
final ScaleMeasurement measurement = scaleMeasurements.get(i);
if (measurement.getDateTime().before(yearStart)) {
this.scaleMeasurements.add(null);
Calendar newCalendar = Calendar.getInstance();
newCalendar.setTime(measurement.getDateTime());
calendar.set(Calendar.YEAR, newCalendar.get(Calendar.YEAR));
yearStart = calendar.getTime();
}
this.scaleMeasurements.add(measurement);
}
notifyDataSetChanged();
}
@Override
public int getCount() {
return measurementsToShow;
}
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LinearLayout row = new LinearLayout(getContext());
row.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
@Override
public Object getItem(int position) {
return scaleMeasurements.get(position);
}
final int screenSize = getResources().getConfiguration()
.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
final boolean isSmallScreen =
screenSize != Configuration.SCREENLAYOUT_SIZE_XLARGE
&& screenSize != Configuration.SCREENLAYOUT_SIZE_LARGE;
@Override
public long getItemId(int position) {
return scaleMeasurements.get(position).getId();
}
final int count = viewType == VIEW_TYPE_YEAR ? 1 : visibleMeasurements.size();
for (int i = 0; i < count; ++i) {
TextView column = new TextView(getContext());
column.setLayoutParams(new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Create entries in stringCache if needed
if (stringCache[position][0] == null) {
ScaleMeasurement measurement = scaleMeasurements.get(position);
ScaleMeasurement prevMeasurement = null;
if (position + 1 < scaleMeasurements.size()) {
prevMeasurement = scaleMeasurements.get(position + 1);
}
for (int i = 0; i < visibleMeasurements.size(); ++i) {
visibleMeasurements.get(i).loadFrom(measurement, prevMeasurement);
SpannableStringBuilder string = new SpannableStringBuilder();
string.append(visibleMeasurements.get(i).getValueAsString(false));
visibleMeasurements.get(i).appendDiffValue(string, true);
stringCache[position][i] = string;
}
}
// Create view if needed
LinearLayout row;
if (convertView == null) {
row = new LinearLayout(getContext());
final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
final boolean isSmallScreen =
screenSize != Configuration.SCREENLAYOUT_SIZE_XLARGE
&& screenSize != Configuration.SCREENLAYOUT_SIZE_LARGE;
for (int i = 0; i < visibleMeasurements.size(); ++i) {
TextView column = new TextView(getContext());
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
1);
layoutParams.width = 0;
column.setLayoutParams(layoutParams);
if (viewType == VIEW_TYPE_MEASUREMENT) {
column.setMinLines(2);
column.setGravity(Gravity.CENTER_HORIZONTAL);
if (isSmallScreen) {
column.setTextSize(COMPLEX_UNIT_DIP, 9);
}
row.addView(column);
}
else {
column.setPadding(0, 10, 0, 10);
column.setGravity(Gravity.CENTER);
column.setTextSize(COMPLEX_UNIT_DIP, 16);
}
row.addView(column);
}
else {
row = (LinearLayout) convertView;
return new ViewHolder(row);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
LinearLayout row = holder.measurementView;
final ScaleMeasurement measurement = scaleMeasurements.get(position);
if (measurement == null) {
ScaleMeasurement nextMeasurement = scaleMeasurements.get(position + 1);
Calendar calendar = Calendar.getInstance();
calendar.setTime(nextMeasurement.getDateTime());
TextView column = (TextView) row.getChildAt(0);
column.setText(String.format("%d", calendar.get(Calendar.YEAR)));
return;
}
ScaleMeasurement prevMeasurement = null;
if (position + 1 < scaleMeasurements.size()) {
prevMeasurement = scaleMeasurements.get(position + 1);
if (prevMeasurement == null) {
prevMeasurement = scaleMeasurements.get(position + 2);
}
}
// Fill view with data
for (int i = 0; i < visibleMeasurements.size(); ++i) {
final MeasurementView view = visibleMeasurements.get(i);
view.loadFrom(measurement, prevMeasurement);
SpannableStringBuilder string = new SpannableStringBuilder();
string.append(view.getValueAsString(false));
view.appendDiffValue(string, true);
TextView column = (TextView) row.getChildAt(i);
column.setText(stringCache[position][i]);
column.setText(string);
}
return row;
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), DataEntryActivity.class);
intent.putExtra(DataEntryActivity.EXTRA_ID, measurement.getId());
startActivity(intent);
}
});
}
@Override
public boolean hasStableIds() {
return true;
public int getItemCount() {
return scaleMeasurements == null ? 0 : scaleMeasurements.size();
}
@Override
public int getItemViewType(int position) {
return scaleMeasurements.get(position) != null ? VIEW_TYPE_MEASUREMENT : VIEW_TYPE_YEAR;
}
}
}

View File

@@ -80,8 +80,9 @@ public class AboutPreferences extends PreferenceFragment {
@Override
protected void log(int priority, String tag, String message, Throwable t) {
writer.printf("%s %s %s: %s\n",
format.format(new Date()), priorityToString(priority), tag, message);
final long id = Thread.currentThread().getId();
writer.printf("%s %s [%d] %s: %s\n",
format.format(new Date()), priorityToString(priority), id, tag, message);
}
}

View File

@@ -232,6 +232,7 @@ public class BluetoothPreferences extends PreferenceFragment {
prefBtDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
stopDiscoveryAndLeScan();
getDebugInfo(device);
return false;
}
@@ -306,7 +307,7 @@ public class BluetoothPreferences extends PreferenceFragment {
super.onStart();
// Restart discovery after e.g. orientation change
if (btScanner.getDialog() != null) {
if (btScanner.getDialog() != null && btScanner.getDialog().isShowing()) {
startBluetoothDiscovery();
}
}

View File

@@ -31,11 +31,12 @@ public class DateMeasurementView extends MeasurementView {
// Don't change key value, it may be stored persistent in preferences
public static final String KEY = "date";
private static final DateFormat dateFormat = DateFormat.getDateInstance();
private final DateFormat dateFormat;
private Date date;
public DateMeasurementView(Context context) {
super(context, R.string.label_date, R.drawable.ic_lastmonth);
dateFormat = DateFormat.getDateInstance();
}
@Override

View File

@@ -1,68 +1,24 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:background="?attr/colorPrimary"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="left"
android:layout_weight="1"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:id="@+id/subpageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="center"
android:layout_weight="0"
android:orientation="horizontal">
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:gravity="right"
android:layout_weight="0.9"
android:orientation="horizontal">
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/tableHeaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingTop="5dp">
</LinearLayout>
android:paddingTop="5dp" />
<LinearLayout
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="?android:attr/listDivider" />
<android.support.v7.widget.RecyclerView
android:id="@+id/tableDataView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.9"
android:gravity="bottom"
android:orientation="horizontal">
<ListView
android:id="@+id/tableDataView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="5dp">
</ListView>
</LinearLayout>
android:scrollbars="vertical" />
</LinearLayout>

View File

@@ -0,0 +1,228 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="title_overview">Oversigt</string>
<string name="title_graph">Diagram</string>
<string name="title_table">Tabel</string>
<string name="title_statistics">Statistik</string>
<string name="title_users">Brugere</string>
<string name="title_measurements">Målinger</string>
<string name="title_about">Om</string>
<string name="action_settings">Indstillinger</string>
<string name="action_bluetooth_status">Bluetooth-status</string>
<string name="label_cancel">Afslut</string>
<string name="label_ok">Ok</string>
<string name="label_yes">Ja</string>
<string name="label_no">Nej</string>
<string name="label_delete">Slet</string>
<string name="label_add_user">Tilføj bruger</string>
<string name="label_weight">Kropsvægt</string>
<string name="label_bmi">Body Mass Index (BMI)</string>
<string name="label_bmr">Basalstofskifte (BMR)</string>
<string name="label_fat">Kropsfedt</string>
<string name="label_water">Kropsvæske</string>
<string name="Monday">Mandag</string>
<string name="Tuesday">Tirsdag</string>
<string name="Wednesday">Onsdag</string>
<string name="Thursday">Torsdag</string>
<string name="Friday">Fredag</string>
<string name="Saturday">Lørdag</string>
<string name="Sunday">Søndag</string>
<string name="label_backup">Sikkerhedskopi</string>
<string name="default_value_reminder_notify_text">Dato for indvejning</string>
<string name="label_reminder_time">Tid</string>
<string name="label_reminder_weekdays">Dage</string>
<string name="label_website">Hjemmeside</string>
<string name="label_license">Licens</string>
<string name="error_exporting">Fejl i eksport</string>
<string name="label_gender">Køn</string>
<string name="label_male">Mand</string>
<string name="label_female">Kvinde</string>
<string name="label_goal_weight">Målvægt</string>
<string name="label_goal_date">Måldato</string>
<string name="label_title_user">Bruger</string>
<string name="label_title_last_measurement">Seneste vejning</string>
<string name="label_title_goal">Mål</string>
<string name="label_date">Dato</string>
<string name="label_time">Tid</string>
<string name="label_birthday">Fødselsdag</string>
<string name="label_user_name">Navn</string>
<string name="label_height">Højde</string>
<string name="label_scale_unit">Vægtenhed</string>
<string name="label_days_left">Dage tilbage</string>
<string name="label_goal_date_is">Måldatoen:</string>
<string name="label_weight_difference">Vægtforskel</string>
<plurals name="label_days">
<item quantity="one">%d dag</item>
<item quantity="other">%d dage</item>
</plurals>
<string name="label_last_week">Gennemsnit den seneste uge</string>
<string name="label_last_month">Gennemsnit den seneste måned</string>
<string name="label_muscle">Muskler</string>
<string name="label_lbm">Fedtfri kropsvægt</string>
<string name="label_waist">Taljemål</string>
<string name="label_hip">Hofteomkreds</string>
<string name="label_comment">Kommentar</string>
<string name="label_whr">Talje-hofte ratio</string>
<string name="label_whtr">Talje-højde ratio</string>
<string name="label_bone">Knoglemasse</string>
<string name="label_smartUserAssign">Smart tildeling af brugere</string>
<string name="label_import">Import</string>
<string name="label_export">Eksport</string>
<string name="label_delete_all">Slet alle</string>
<string name="error_value_required">Obligatorisk</string>
<string name="error_value_range">Værdien er uden for intervallet</string>
<string name="error_importing">Fejl ved import</string>
<string name="error_user_name_required">Fejl: navn er påkrævet</string>
<string name="error_height_required">Fejl: højde er påkrævet</string>
<string name="error_initial_weight_required">Fejl: vægt er påkrævet</string>
<string name="error_goal_weight_required">Fejl: målvægt er påkrævet</string>
<string name="info_data_deleted">Data er slettet</string>
<string name="info_data_all_deleted">Alle data er blevet slettet</string>
<string name="info_data_exported">Eksportér til</string>
<string name="info_data_imported">Importér fra</string>
<string name="info_enter_value_in">Værdien i</string>
<string name="info_enter_comment">Kommentar</string>
<string name="info_is_visible">er synlig</string>
<string name="info_is_not_visible">er ikke synlig</string>
<string name="info_is_enable">aktiveret</string>
<string name="info_is_not_enable">deaktiveret</string>
<string name="info_is_not_available">ikke tilgængelig</string>
<string name="info_bluetooth_try_connection">Forbinder til:</string>
<string name="info_bluetooth_connection_lost">Ingen forbindelse til Bluetooth enhed</string>
<string name="info_bluetooth_no_device">Ingen Bluetooth-enhed fundet</string>
<string name="info_bluetooth_connection_successful">Forbindelse etableret</string>
<string name="info_bluetooth_init">Initierer Bluetooth enhed</string>
<string name="info_bluetooth_connection_error">Uventet Bluetooth fejl</string>
<string name="info_new_data_added">Tilføjede %1$.2f%2$s [%3$s] til %4$s</string>
<string name="info_enter_user_name">Dit navn</string>
<string name="info_no_selected_user">Vælg en bruger under indstillinger</string>
<string name="info_no_evaluation_available">Ingen evaluering til rådighed</string>
<string name="question_really_delete">Ønsker du at slette data?</string>
<string name="question_really_delete_all">Ønsker du at slette alle data?</string>
<string name="question_really_delete_user">Ønsker du at slette brugeren?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Aktivér Bluetoth</string>
<string name="label_bluetooth_searching">Etablerer Bluetooth forbindelse</string>
<string name="label_enable_labels">Data labels</string>
<string name="label_enable_points">Datapunkter</string>
<string name="label_delete_confirmation">Bekræft sletning</string>
<string name="label_category_measurement_database">Mållingsdatabase</string>
<string name="label_maintainer">Udviklere</string>
<string name="label_automatic">auto</string>
<string name="label_reminder">Påmindelse</string>
<string name="label_reminder_notify_text">Meddelelsestekst</string>
<string name="info_on_date"></string>
<string name="label_bt_device_no_support">enheden er ikke understøttet</string>
<string name="label_exportBackup">Exportér sikkerhedskopi</string>
<string name="label_importBackup">Importér sikkerhedskopi</string>
<string name="label_export_dir">Eksportmappe</string>
<string name="label_not_found">blev ikke fundet</string>
<string name="label_ignoreOutOfRange">Ignorér data udenfor interval</string>
<string name="label_initial_weight">Startvægt</string>
<string name="label_regression_line">Vægt tilbagegang</string>
<string name="label_regression_line_degree">Grad af tilbagegang</string>
<string name="label_goal_line">Mållinje</string>
<string name="error_max_scale_users">Det maksimale antal brugere er nået</string>
<string name="info_step_on_scale">Træd op på vægten med bare fødder for at foretagen en referencemåling</string>
<string name="info_measuring">Måler vægt: %.2f</string>
<string name="open_drawer">Åben</string>
<string name="close_drawer">Luk</string>
<string name="info_bluetooth_no_device_set">Ingen Bluetooth enhed er valgt</string>
<string name="info_new_data_duplicated">En måling med samme dato og tidspunkt findes allerede</string>
<string name="title_general">Generelt</string>
<string name="error_user_name_too_short">Fejl: navnet skal være mindst tre tegn langt</string>
<string name="label_theme">Tema</string>
<string name="label_feedback_message_enjoying">Er du tilfreds med openScale?</string>
<string name="label_feedback_message_rate_app">Vil du rate app'en på Google Play eller GitHub?</string>
<string name="label_feedback_message_issue">Send feedback?</string>
<string name="label_feedback_message_yes">Ja</string>
<string name="label_feedback_message_no">Nej</string>
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">Nej tak</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">En uventet fejl er opstået.
\n
\nOpret venligst en ny sag
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Genstart app</string>
<string name="customactivityoncrash_error_activity_close_app">Luk app</string>
<string name="customactivityoncrash_error_activity_error_details">Fejlinformation</string>
<string name="customactivityoncrash_error_activity_error_details_title">Fejlinformation</string>
<string name="customactivityoncrash_error_activity_error_details_close">Luk</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Kopiér til udklipsholder</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Kopieret til udklipsholder</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Fejlinformation</string>
<string name="save">Gem</string>
<string name="label_share">Del</string>
<string name="label_bluetooth_searching_finished">Søgning af sluttet</string>
<string name="label_help">Hjælp</string>
<string name="toggle_expand">Fold ud</string>
<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_title">Information</string>
<string name="label_share_subject">CSV dataeksport (%s)</string>
<string name="label_next">Næste</string>
<string name="label_set_default_order">Indstil standardsortéring</string>
<string name="label_weeks_view">ugentlig</string>
<string name="label_mergeWithLastMeasurement">Flet med tidligere måling</string>
<string name="label_export_overwrite">Overskriv tidligere eksport \"%s\"?</string>
<string name="label_include_in_overview_graph">Vis i oversigtsdiagram</string>
<string name="label_measurement_in_percent">Målt i %</string>
<string name="label_estimate_measurement">Estimatmåling</string>
<string name="label_estimation_formula">Estimeringsformel</string>
<string name="language_default">Systemstandard</string>
<string name="label_language">Sprog</string>
<string name="theme_light">Lyst</string>
<string name="theme_dark">Mørkt</string>
<string name="label_contribute_translation">Bidrag med oversættelse</string>
<string name="label_add_or_fix_translation">Tilføj eller ret oversættelse</string>
<string name="label_overview_graph">Oversigtsdiagram</string>
<string name="label_percent">Procent</string>
<string name="label_estimated">Skønsmæssigt</string>
<string name="label_estimate_measurement_summary">Baseret på vægt, højde, alder, køn mv.</string>
<string name="label_scale_not_supported">Denne personvægt er ikke understøttet</string>
<string name="label_click_to_help_add_support">Klik for at hjælpe med at tilføje understøttelse af personvægten</string>
<string name="label_auto_backup">Automatisk sikkerhedskopiering</string>
<string name="label_auto_backup_schedule">Planlæg sikkerhedskopiering</string>
<string name="label_overwrite_backup">Overskriv tidligere sikkerhedskopi</string>
<string name="label_daily">daglig</string>
<string name="label_weekly">ugentlig</string>
<string name="label_monthly">månedsvis</string>
<string name="label_development">Udvikling</string>
<string name="label_debug_log">Gem fejlfindingslog</string>
<string name="label_your_bluetooth_scale">Din Bluetooth personvægt</string>
<string name="label_long_press_drag_reorder">Tryk, og træk til den ønskede position</string>
<string name="label_click_measurement_configure">Klik på målinger for at konfigurere</string>
<string name="label_select_measurement">Vælg målenhed</string>
<string name="label_select_user">Vælg bruger</string>
<string name="label_configure_widget">Konfigurér widget</string>
<string name="label_add_measurement">Tilføj måling</string>
<string name="label_visceral_fat">Visceralt fedt omkring indre organer</string>
<string name="label_chest">Brystomkreds</string>
<string name="label_thigh">Låromkreds</string>
<string name="label_biceps">Bicepsomkreds</string>
<string name="label_neck">Halsomkreds</string>
<string name="label_fat_caliper">Hudfoldsmåling</string>
<string name="label_caliper1_male">Bryst hudmåling</string>
<string name="label_caliper2_male">Mave hudmåling</string>
<string name="label_caliper3_male">Lår hudmåling</string>
<string name="label_caliper1_female">Triceps hudmåling</string>
<string name="label_caliper2_female">Mave hudmåling</string>
<string name="label_caliper3_female">Hofte hudmåling</string>
<string name="label_measure_unit">Målenhed</string>
<string name="label_activity_level">Aktivitetsniveau</string>
<string name="activity_level_sedentary">Stillesiddende</string>
<string name="activity_level_mild">let</string>
<string name="activity_level_moderate">Moderat</string>
<string name="activity_level_heavy">hård</string>
<string name="activity_level_extreme">Ekstrem</string>
</resources>

View File

@@ -21,7 +21,7 @@
<string name="label_bmi">IMC</string>
<string name="label_fat">Graisse corporelle</string>
<string name="label_water">Pourcentage d\'eau</string>
<string name="label_muscle">Pourcentage musculaire</string>
<string name="label_muscle">Muscle</string>
<string name="label_waist">Tour de taille</string>
<string name="label_hip">Tour de hanches</string>
<string name="label_comment">Commentaire</string>
@@ -33,8 +33,8 @@
<item quantity="one">%d jour</item>
<item quantity="other">%d jours</item>
</plurals>
<string name="label_last_week">7 derniers jours</string>
<string name="label_last_month">30 derniers jours</string>
<string name="label_last_week">Moyenne sur la dernière semaine</string>
<string name="label_last_month">Moyenne sur le mois dernier</string>
<string name="label_weight_difference">Différence de poids</string>
<string name="label_goal_date_is">La date de l\'objectif :</string>
<string name="label_days_left">Jours restants</string>
@@ -54,14 +54,14 @@
<string name="label_title_user">utilisateur</string>
<string name="label_title_last_measurement">dernière mesure</string>
<string name="label_title_goal">objectif</string>
<string name="label_title_goal">Objectif</string>
<string name="label_import">Importer</string>
<string name="label_export">Exporter</string>
<string name="label_delete_all">Tout supprimer</string>
<string name="error_value_required">Valeur requise</string>
<string name="error_value_range">La valeur n\'est pas dans la plage</string>
<string name="error_value_range">Valeur hors plage</string>
<string name="error_exporting">Erreur durant l\'exportation</string>
<string name="error_importing">Erreur durant l\'importation</string>
<string name="error_user_name_required">Erreur : Nom d\'utilisateur requis</string>
@@ -70,8 +70,8 @@
<string name="info_data_deleted">L\'entrée a été supprimée</string>
<string name="info_data_all_deleted">Toutes les entrées ont été supprimées</string>
<string name="info_data_exported">Données exportées vers</string>
<string name="info_data_imported">Donnéees importées depuis</string>
<string name="info_data_exported">Exporté vers</string>
<string name="info_data_imported">Importé depuis</string>
<string name="info_enter_value_in">Valeur en</string>
<string name="info_enter_comment">Commentaire optionnel</string>
<string name="info_is_visible">est visible</string>
@@ -103,10 +103,10 @@
<string name="label_delete_confirmation">Supprimer la confirmation</string>
<string name="label_reminder">Rappel</string>
<string name="label_reminder_weekdays">Jours de la semaine</string>
<string name="label_reminder_weekdays">Jours</string>
<string name="label_reminder_time">Heure</string>
<string name="label_reminder_notify_text">Intitulé de la notification</string>
<string name="default_value_reminder_notify_text">C\'est l\'heure de se peser!</string>
<string name="default_value_reminder_notify_text">C\'est l\'heure de se peser !</string>
<string name="info_on_date">le</string>
@@ -144,7 +144,7 @@
<string name="save">Sauvegarder</string>
<string name="permission_not_granted">Permission non accordée</string>
<string name="permission_bluetooth_info_title">Information</string>
<string name="permission_bluetooth_info_title">Info</string>
<string name="label_next">Suivant</string>
<string name="title_about">À propos</string>
<string name="label_share">Partager</string>
@@ -176,10 +176,10 @@
<string name="label_lbm">Masse sans graisse</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] to %4$s rajouté</string>
<string name="info_new_data_duplicated">des mesures à la même date et heure existent déjà</string>
<string name="info_new_data_duplicated">une mesure à la même date et heure existe déjà</string>
<string name="label_mergeWithLastMeasurement">Fusionner avec la dernière mesure</string>
<string name="label_bluetooth_searching">Détection de pèse personne en cours</string>
<string name="label_bluetooth_searching">Détection de votre pèse-personne Bluetooth en cours</string>
<string name="label_bluetooth_searching_finished">Détection finalisé</string>
<string name="label_category_measurement_database">Base de données de mesures</string>
@@ -223,4 +223,30 @@
<string name="label_click_to_help_add_support">Cliquer ici pour contribuer</string>
<string name="label_development">Développement</string>
<string name="label_debug_log">Enregistrez les données de débogage dans un fichier</string>
<string name="label_add_measurement">Ajouter une mesure</string>
<string name="label_long_press_drag_reorder">Effectuez un appui long suivi d\'un déplacement des mesures pour les réorganiser</string>
<string name="label_click_measurement_configure">Cliquez sur une mesure pour la configurer</string>
<string name="label_your_bluetooth_scale">Votre pèse-personne Bluetooth</string>
<string name="label_select_measurement">Sélectionner une mesure</string>
<string name="label_select_user">Sélectionner un utilisateur</string>
<string name="label_configure_widget">Configurer le widget</string>
<string name="label_visceral_fat">Graisse viscérale</string>
<string name="label_chest">Tour de poitrine</string>
<string name="label_thigh">Tour de cuisse</string>
<string name="label_biceps">Tour des biceps</string>
<string name="label_neck">Tour du cou</string>
<string name="label_fat_caliper">Pince adipomètre</string>
<string name="label_caliper1_male">Pli cutané de la poitrine</string>
<string name="label_caliper2_male">Pli cutané abdominal</string>
<string name="label_caliper3_male">Pli cutané de la cuisse</string>
<string name="label_caliper1_female">Pli cutané du triceps</string>
<string name="label_caliper2_female">Pli cutané abdominal</string>
<string name="label_caliper3_female">Pli cutané à la hanche</string>
<string name="label_measure_unit">Unité de mesure</string>
<string name="label_activity_level">Niveau d\'activité</string>
<string name="activity_level_sedentary">Sédentaire</string>
<string name="activity_level_mild">Faible</string>
<string name="activity_level_moderate">Modéré</string>
<string name="activity_level_heavy">Important</string>
<string name="activity_level_extreme">Extrême</string>
</resources>

View File

@@ -0,0 +1,246 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="title_overview">Pregled</string>
<string name="title_graph">Grafikon</string>
<string name="title_table">Tablica</string>
<string name="title_statistics">Statistika</string>
<string name="title_users">Korisnici</string>
<string name="title_measurements">Mjerenja</string>
<string name="title_about">O programu</string>
<string name="title_general">Općenito</string>
<string name="action_settings">Postavke</string>
<string name="action_bluetooth_status">Bluetooth Status</string>
<string name="open_drawer">otvori</string>
<string name="close_drawer">zatvori</string>
<string name="label_cancel">Odustani</string>
<string name="label_ok">U redu</string>
<string name="label_yes">Da</string>
<string name="label_no">Ne</string>
<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">Podjeli</string>
<string name="label_share_subject">CSV izvoz (%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>
<string name="label_fat">Masnoća</string>
<string name="label_water">Voda</string>
<string name="label_muscle">Mišići</string>
<string name="label_lbm">Težina bez masnoća</string>
<string name="label_waist">Opseg struka</string>
<string name="label_hip">Opseg kukova</string>
<string name="label_comment">Komentar</string>
<string name="label_whtr">Omjer struka i visine</string>
<string name="label_whr">Omjer struka i kukova</string>
<string name="label_bone">Masa kostiju</string>
<string name="label_smartUserAssign">Pametna dodjela korisnika</string>
<plurals name="label_days">
<item quantity="one">Jedan</item>
<item quantity="few">Nekoliko</item>
<item quantity="other">Ostalo</item>
</plurals>
<string name="label_last_week">Prosjek prošlog tjedna</string>
<string name="label_last_month">Prosjek prošlog mjeseca</string>
<string name="label_weight_difference">Razlika težine</string>
<string name="label_goal_date_is">Ciljani datum:</string>
<string name="label_days_left">Preostalo dana</string>
<string name="label_date">Datum</string>
<string name="label_time">Vrijeme</string>
<string name="label_birthday">Datum rođenja</string>
<string name="label_user_name">Ime</string>
<string name="label_height">Visina</string>
<string name="label_scale_unit">Mjerne jedinice</string>
<string name="label_gender">Spol</string>
<string name="label_male">Muški</string>
<string name="label_female">Ženski</string>
<string name="label_goal_weight">Ciljana težina</string>
<string name="label_goal_date">Ciljani datum</string>
<string name="label_title_user">korisnik</string>
<string name="label_title_last_measurement">zadnje mjerenje</string>
<string name="label_title_goal">Cilj</string>
<string name="label_import">Uvoz</string>
<string name="label_export">Izvoz</string>
<string name="label_delete_all">Izbriši sve</string>
<string name="error_value_required">Potrebna vrijednost</string>
<string name="error_value_range">Vrijednost izvan granica</string>
<string name="error_exporting">Greška u izvozu</string>
<string name="error_importing">Greška u uvozu</string>
<string name="error_user_name_required">Greška: Unesite ime</string>
<string name="error_user_name_too_short">Greška: Ime mora imati barem 3 znaka</string>
<string name="error_height_required">Greška: Unesite težinu</string>
<string name="error_initial_weight_required">Greška: Unesite početnu težinu</string>
<string name="error_goal_weight_required">Greška: Unesite ciljanu težinu</string>
<string name="info_data_deleted">Stavka obrisana</string>
<string name="info_data_all_deleted">Sve stavke obrisane</string>
<string name="info_data_exported">Izvezeno u</string>
<string name="info_data_imported">Uvezeno iz</string>
<string name="info_enter_value_in">Vrijednost u</string>
<string name="info_enter_comment">Komentar (neobavezan)</string>
<string name="info_is_visible">je vidljivo</string>
<string name="info_is_not_visible">nije vidljivo</string>
<string name="info_is_enable">uključeno</string>
<string name="info_is_not_enable">isključeno</string>
<string name="info_is_not_available">nedostupno</string>
<string name="info_bluetooth_try_connection">Spajanje na:</string>
<string name="info_bluetooth_connection_lost">Izgubljena Bluetooth veza</string>
<string name="info_bluetooth_no_device">Nije pronađen nijedan uređaj</string>
<string name="info_bluetooth_no_device_set">Nije odabran nijedan uređaj</string>
<string name="info_bluetooth_connection_successful">Veza uspostavljena</string>
<string name="info_bluetooth_init">Inicijalizacija Bluetooth uređaja</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_duplicated">mjerenje s istim datumom i vremenom već postoji</string>
<string name="info_enter_user_name">Vaše ime</string>
<string name="info_no_selected_user">Ne postoji niti jedan korisnik. Stvorite ga u postavkama.</string>
<string name="info_no_evaluation_available">Nije moguće procijeniti vrijednost</string>
<string name="question_really_delete">Izbrisati stavku?</string>
<string name="question_really_delete_all">Izbrisati sve stavke svih korisnika ?</string>
<string name="question_really_delete_user">Izbrisati korisnika?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Povezivanje sa vagom kod pokretanja</string>
<string name="label_mergeWithLastMeasurement">Spoji sa zadnjim mjerenjem</string>
<string name="label_bluetooth_searching">Tražim Vašu Bluetooth vagu</string>
<string name="label_bluetooth_searching_finished">Pretraga završena</string>
<string name="label_enable_labels">Oznake</string>
<string name="label_enable_points">Točka</string>
<string name="label_delete_confirmation">Potvrda brisanja</string>
<string name="label_category_measurement_database">Baza podataka</string>
<string name="label_maintainer">Održavatelj</string>
<string name="label_website">Web stranica</string>
<string name="label_license">Licenca</string>
<string name="label_automatic">automatski</string>
<string name="label_theme">Tema</string>
<string name="label_reminder">Podsjetnik</string>
<string name="label_reminder_weekdays">Dani</string>
<string name="label_reminder_time">Vrijeme</string>
<string name="label_reminder_notify_text">Tekst obavijesti</string>
<string name="default_value_reminder_notify_text">Vaganje za</string>
<string name="info_on_date">u</string>
<string name="Monday">Ponedjeljak</string>
<string name="Tuesday">Utorak</string>
<string name="Wednesday">Srijeda</string>
<string name="Thursday">Četvrtak</string>
<string name="Friday">Petak</string>
<string name="Saturday">Subota</string>
<string name="Sunday">Nedjelja</string>
<string name="label_bt_device_no_support">uređaj nije podržan</string>
<string name="label_exportBackup">Izvoz sigurnosne kopije</string>
<string name="label_importBackup">Uvoz sigurnosne kopije</string>
<string name="label_backup">Sigurnosna kopija</string>
<string name="label_export_dir">Mapa za izvoz</string>
<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_regression_line">Linija regresije</string>
<string name="label_regression_line_degree">Polinom regresije</string>
<string name="label_goal_line">Ciljana linija</string>
<string name="label_help">Pomoć</string>
<string name="label_feedback_message_enjoying">Preporučate openScale?</string>
<string name="label_feedback_message_rate_app">A ocjena na Google Play ili GitHub ?</string>
<string name="label_feedback_message_issue">Želite li dati svoje mišljenje ?</string>
<string name="label_feedback_message_yes">Da</string>
<string name="label_feedback_message_no">Baš i ne</string>
<string name="label_feedback_message_positive">U redu</string>
<string name="label_feedback_message_negative">Ne, hvala</string>
<string name="error_max_scale_users">Dosegnut maksimalni broj korisnika</string>
<string name="info_step_on_scale">Stanite bosi na vagu radi referentnog mjerenja</string>
<string name="info_measuring">Mjerenje težine: %.2f</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Došlo je do neočekivane pogreške
\n
\nMolimo da prijavite problem sa podacima o grešci na
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Ponovo pokreni</string>
<string name="customactivityoncrash_error_activity_close_app">Zatvori</string>
<string name="customactivityoncrash_error_activity_error_details">Detalji o pogrešci</string>
<string name="customactivityoncrash_error_activity_error_details_title">Detalji o pogrešci</string>
<string name="customactivityoncrash_error_activity_error_details_close">Zatvori</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Kopiraj u međuspremnik</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Kopirano u međuspremnik</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Informacije o pogrešci</string>
<string name="toggle_expand">Proširi</string>
<string name="edit">Uredi</string>
<string name="save">Spremi</string>
<string name="label_month_view">Mjesec</string>
<string name="label_weeks_view">Tjedan</string>
<string name="permission_not_granted">Nije dozvoljeno</string>
<string name="permission_bluetooth_info">Dozvola pristupu lokaciji je potrebna za traženje Bluetooth uređaja. Može se opet zabraniti pristup nakon pronalaska uređaja.</string>
<string name="permission_bluetooth_info_title">Info</string>
<string name="label_next">Sljedeći</string>
<string name="label_long_press_drag_reorder">Dugo pritisnite i povucite mjerenja za promjenu redoslijeda</string>
<string name="label_click_measurement_configure">Odaberite mjerenje za uređivanje</string>
<string name="label_set_default_order">Zadan redosljed</string>
<string name="label_export_overwrite">Prebriši prijašnji izvoz \"%s\"?</string>
<string name="label_include_in_overview_graph">Prikaži na pregledu grafa</string>
<string name="label_measurement_in_percent">Mjerenja u %</string>
<string name="label_estimate_measurement">Procjenjeno mjerenje</string>
<string name="label_estimation_formula">Formula za procjenu</string>
<string name="language_default">Zadana postavka</string>
<string name="label_language">Jezik</string>
<string name="theme_light">Svjetlo</string>
<string name="theme_dark">Tamno</string>
<string name="label_contribute_translation">Pomognite prevesti</string>
<string name="label_add_or_fix_translation">Dodaj novi ili popravi postojeći</string>
<string name="label_overview_graph">Pregled grafikona</string>
<string name="label_percent">Posto</string>
<string name="label_estimated">Procjena</string>
<string name="label_estimate_measurement_summary">Temeljeno na težini, visini, dobi, spolu, itd.</string>
<string name="label_auto_backup">Automatska sigurnosna kopija</string>
<string name="label_auto_backup_schedule">Raspored sigurnosne kopije</string>
<string name="label_overwrite_backup">Prebriši prijašnju sigurnosnu kopiju</string>
<string name="label_daily">dnevno</string>
<string name="label_weekly">tjedno</string>
<string name="label_monthly">mjesečno</string>
<string name="label_scale_not_supported">Da li je Vaša vaga podržana ?</string>
<string name="label_click_to_help_add_support">Pomognite da bi ju podržali</string>
<string name="label_development">Razvoj</string>
<string name="label_debug_log">Spremi log zapis u datoteku</string>
<string name="label_your_bluetooth_scale">Vaša Bluetooth vaga</string>
<string name="label_select_measurement">Odaberite mjerenje</string>
<string name="label_select_user">Odaberite korisnika</string>
<string name="label_configure_widget">Podesiti widget</string>
<string name="label_visceral_fat">Visceralne masnoće</string>
<string name="label_chest">Opseg prsa</string>
<string name="label_thigh">Opseg bedra</string>
<string name="label_biceps">Opseg bicepsa</string>
<string name="label_neck">Opseg vrata</string>
<string name="label_fat_caliper">Kaliper za mjerenje nabora</string>
<string name="label_caliper1_male">Nabor na prsima</string>
<string name="label_caliper2_male">Nabor na trbuhu</string>
<string name="label_caliper3_male">Nabor na bedru</string>
<string name="label_caliper1_female">Nabor na tricepsu</string>
<string name="label_caliper2_female">Nabor na trbuhu</string>
<string name="label_caliper3_female">Nabor na kukovima</string>
<string name="label_measure_unit">Mjerna jedinica</string>
<string name="label_activity_level">Razina aktivnosti</string>
<string name="activity_level_sedentary">Sjedeći</string>
<string name="activity_level_mild">Blag</string>
<string name="activity_level_moderate">Umjeren</string>
<string name="activity_level_heavy">Jak</string>
<string name="activity_level_extreme">Ekstreman</string>
</resources>

View File

@@ -0,0 +1,247 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="title_overview">Pregled</string>
<string name="title_graph">Diagram</string>
<string name="title_table">Tabela</string>
<string name="title_statistics">Statistika</string>
<string name="title_users">Uporabniki</string>
<string name="title_measurements">Meritve</string>
<string name="title_about">Informacije</string>
<string name="title_general">Splošno</string>
<string name="action_settings">Nastavitve</string>
<string name="action_bluetooth_status">Bluetooth nastavitve</string>
<string name="open_drawer">odpri</string>
<string name="close_drawer">zapri</string>
<string name="label_cancel">Prekliči</string>
<string name="label_ok">OK</string>
<string name="label_yes">Da</string>
<string name="label_no">Ne</string>
<string name="label_delete">Izbriši</string>
<string name="label_add_user">Dodaj uporabnika</string>
<string name="label_add_measurement">Dodaj meritev</string>
<string name="label_share">Deli</string>
<string name="label_share_subject">CSV izvoz podatkov (%s)</string>
<string name="label_weight">Teža</string>
<string name="label_bmi">Indeks telesne mase (ITM)</string>
<string name="label_bmr">Bazalni metabolizem (BM)</string>
<string name="label_fat">Telesna maščoba</string>
<string name="label_water">Vsebnost vode</string>
<string name="label_muscle">Mišice</string>
<string name="label_lbm">Čvrsta telesna masa</string>
<string name="label_waist">Obseg pasu</string>
<string name="label_hip">Obseg bokov</string>
<string name="label_comment">Komentar</string>
<string name="label_whtr">Razmerje trebuh - višina</string>
<string name="label_whr">Razmerje trebuh - boki</string>
<string name="label_bone">Masa kosti</string>
<string name="label_smartUserAssign">Pametna določitev uporabnika</string>
<plurals name="label_days">
<item quantity="one">%d dan</item>
<item quantity="two">%d dneva</item>
<item quantity="few">%d dnevi</item>
<item quantity="other"/>
</plurals>
<string name="label_last_week">Povprečje prejšnjega tedna</string>
<string name="label_last_month">Povprečje prejšnjega meseca</string>
<string name="label_weight_difference">Razlika teže</string>
<string name="label_goal_date_is">Datum cilja:</string>
<string name="label_days_left">Preostali dnevi</string>
<string name="label_date">Datum</string>
<string name="label_time">Čas</string>
<string name="label_birthday">Rojstni dan</string>
<string name="label_user_name">Ime</string>
<string name="label_height">Višina</string>
<string name="label_scale_unit">Enote</string>
<string name="label_gender">Spol</string>
<string name="label_male">Moški</string>
<string name="label_female">Ženski</string>
<string name="label_goal_weight">Ciljna teža</string>
<string name="label_goal_date">Ciljni datum</string>
<string name="label_title_user">uporabnik</string>
<string name="label_title_last_measurement">zadnja meritev</string>
<string name="label_title_goal">Cilj</string>
<string name="label_import">Uvozi</string>
<string name="label_export">Izvozi</string>
<string name="label_delete_all">Izbriši vse</string>
<string name="error_value_required">Zahtevana vrednost</string>
<string name="error_value_range">Vrednost je zunaj meje</string>
<string name="error_exporting">Napaka pri izvozu</string>
<string name="error_importing">Napaka pri uvozu</string>
<string name="error_user_name_required">Napaka: zahtevno ime</string>
<string name="error_user_name_too_short">Napaka: Ime mora imeti vsaj 3 znake</string>
<string name="error_height_required">Napaka: Višina zahtevana</string>
<string name="error_initial_weight_required">Napaka: Začetna teža zahtevana</string>
<string name="error_goal_weight_required">Napaka: Ciljna teža zahtevana</string>
<string name="info_data_deleted">Vnos je izbrisan</string>
<string name="info_data_all_deleted">Vsi vnosi so izbrisani</string>
<string name="info_data_exported">Izvozi v</string>
<string name="info_data_imported">Uvozi iz</string>
<string name="info_enter_value_in">Vrednost med</string>
<string name="info_enter_comment">Komentiraj(ni zahtevano)</string>
<string name="info_is_visible">je viden</string>
<string name="info_is_not_visible">ni viden</string>
<string name="info_is_enable">omogočeno</string>
<string name="info_is_not_enable">onemogočeno</string>
<string name="info_is_not_available">ni na voljo</string>
<string name="info_bluetooth_try_connection">Poveži z:</string>
<string name="info_bluetooth_connection_lost">Bluetooth povezava izgubljena</string>
<string name="info_bluetooth_no_device">Nobena Bluetooth naprava ni bila najdena</string>
<string name="info_bluetooth_no_device_set">Nobena Bluetooth naprava ni izbrana</string>
<string name="info_bluetooth_connection_successful">Povezava vzpostavljena</string>
<string name="info_bluetooth_init">Zaženi Bluetooth napravo</string>
<string name="info_bluetooth_connection_error">Nepričakovana Bluetooth napaka</string>
<string name="info_new_data_added">%1$.2f%2$s [%3$s] to %4$s dodan</string>
<string name="info_new_data_duplicated">meritev z istim datumom in časom že obstaja</string>
<string name="info_enter_user_name">Tvoje ime</string>
<string name="info_no_selected_user">Trenutno še ne obstaja noben uporabnik. Prosim, ustvarite ga v nastavitvah.</string>
<string name="info_no_evaluation_available">Vrednosti ni bilo mogoče določiti</string>
<string name="question_really_delete">Želite izbrisati vnos?</string>
<string name="question_really_delete_all">Želite izbrisati vse vnose vseh uporabnikov?</string>
<string name="question_really_delete_user">Izbriši uporabnika?</string>
<string name="label_bluetooth_title">Bluetooth</string>
<string name="label_bluetooth_enable">Poveži se s tehtnico ob zagonu aplikacije</string>
<string name="label_mergeWithLastMeasurement">Združi z zadnjimi meritvami</string>
<string name="label_bluetooth_searching">Iščem Bluetooth tehtnico</string>
<string name="label_bluetooth_searching_finished">Iskanje končano</string>
<string name="label_enable_labels">Podatkovna oznaka</string>
<string name="label_enable_points">Podatek</string>
<string name="label_delete_confirmation">Izbriši potrditev</string>
<string name="label_category_measurement_database">Baza meritev</string>
<string name="label_maintainer">Vzdrževalec</string>
<string name="label_website">Spletna stran</string>
<string name="label_license">Licenca</string>
<string name="label_automatic">Avtomatsko</string>
<string name="label_theme">Tema</string>
<string name="label_reminder">Opomnik</string>
<string name="label_reminder_weekdays">Dnevi</string>
<string name="label_reminder_time">Čas</string>
<string name="label_reminder_notify_text">Besedilo obvestila</string>
<string name="default_value_reminder_notify_text">Čas za tehtanje</string>
<string name="info_on_date">v</string>
<string name="Monday">ponedeljek</string>
<string name="Tuesday">torek</string>
<string name="Wednesday">sreda</string>
<string name="Thursday">četrtek</string>
<string name="Friday">petek</string>
<string name="Saturday">sobota</string>
<string name="Sunday">nedelja</string>
<string name="label_bt_device_no_support">Naprava ni podprta</string>
<string name="label_exportBackup">Izvozi varnostno datoteko</string>
<string name="label_importBackup">Uvozi varnostno datoteko</string>
<string name="label_backup">Varnostna datoteka</string>
<string name="label_export_dir">Izvozi mapo</string>
<string name="label_not_found">nič ni bilo najdeno</string>
<string name="label_ignoreOutOfRange">Ignoriraj vrednosti zunaj meje</string>
<string name="label_initial_weight">Začetna teža</string>
<string name="label_regression_line">Regresijska krivulja teže</string>
<string name="label_regression_line_degree">Regresija polinomske stopnje</string>
<string name="label_goal_line">Ciljna krivulja</string>
<string name="label_help">Pomoč</string>
<string name="label_feedback_message_enjoying">Vam je všeč openScale?</string>
<string name="label_feedback_message_rate_app">Bi želeli podati oceno na Google Play ali GitHub?</string>
<string name="label_feedback_message_issue">Bi nam radi priskrbeli povratno mnenje?</string>
<string name="label_feedback_message_yes">Da</string>
<string name="label_feedback_message_no">Ne zares</string>
<string name="label_feedback_message_positive">OK</string>
<string name="label_feedback_message_negative">Ne, hvala</string>
<string name="error_max_scale_users">Doseženo je bilo maksimalno število sočasnih uporabnikov tehtnice</string>
<string name="info_step_on_scale">Prosim, stopite bosi na tehtnico za referenčno meritev</string>
<string name="info_measuring">Izmerjena teža: %.2f</string>
<string name="customactivityoncrash_error_activity_error_occurred_explanation">Zgodila se je nepričakovana napaka.
\n
\nProsim, ustvarite novo težavo(issue) vključno s podatki o napaki na:
\nhttps://github.com/oliexdev/openScale/issues</string>
<string name="customactivityoncrash_error_activity_restart_app">Ponovno zaženi aplikacijo</string>
<string name="customactivityoncrash_error_activity_close_app">Zapri aplikacijo</string>
<string name="customactivityoncrash_error_activity_error_details">Podatki o napaki</string>
<string name="customactivityoncrash_error_activity_error_details_title">Podatki o napaki</string>
<string name="customactivityoncrash_error_activity_error_details_close">Zapri</string>
<string name="customactivityoncrash_error_activity_error_details_copy">Kopiraj v odložišče</string>
<string name="customactivityoncrash_error_activity_error_details_copied">Kopirano v odložišče</string>
<string name="customactivityoncrash_error_activity_error_details_clipboard_label">Informacije o napaki</string>
<string name="toggle_expand">Spremeni v razširjen pogled</string>
<string name="edit">Uredi</string>
<string name="save">Shrani</string>
<string name="label_month_view">Mesečni pogled</string>
<string name="label_weeks_view">Tedenski pogled</string>
<string name="permission_not_granted">Dovoljenja niso bila pridobljena</string>
<string name="permission_bluetooth_info">Dovoljenje je potrebno za približno določanje lokacije Bluetooth naprave. Dovoljenje je lahko odvzeto takoj po tem, ko je naprava najdena.</string>
<string name="permission_bluetooth_info_title">Informacije</string>
<string name="label_next">Naprej</string>
<string name="label_long_press_drag_reorder">Dolgo držite in potegnite meritve za prerazporejanja</string>
<string name="label_click_measurement_configure">Kliknite meritve za rekonfiguracijo</string>
<string name="label_set_default_order">Nastavi privzet vrstni red</string>
<string name="label_export_overwrite">Zamenjaj prejšnji izvoz \"%s\"?</string>
<string name="label_include_in_overview_graph">Vključi v pregledni graf</string>
<string name="label_measurement_in_percent">Meritev v %</string>
<string name="label_estimate_measurement">Ocena meritve</string>
<string name="label_estimation_formula">Ocenjevalna formula</string>
<string name="language_default">Privzete nastavitve sistema</string>
<string name="label_language">Jezik</string>
<string name="theme_light">Svetlo</string>
<string name="theme_dark">Temno</string>
<string name="label_contribute_translation">Prispevaj prevod</string>
<string name="label_add_or_fix_translation">Dodaj ali popravi obstoječega</string>
<string name="label_overview_graph">Pregledni graf</string>
<string name="label_percent">Procent</string>
<string name="label_estimated">Ocenjeno</string>
<string name="label_estimate_measurement_summary">Bazirano na teži, višini, starosti, spolu, ...</string>
<string name="label_auto_backup">Samodejna varnostna kopija</string>
<string name="label_auto_backup_schedule">Urnik varnostnih kopij</string>
<string name="label_overwrite_backup">Zamenjaj prejšnjo varnostno kopijo</string>
<string name="label_daily">dnevno</string>
<string name="label_weekly">tedensko</string>
<string name="label_monthly">mesečno</string>
<string name="label_scale_not_supported">Je vaša tehtnica že podprta?</string>
<string name="label_click_to_help_add_support">Kliknite tukaj, če želite pomagati</string>
<string name="label_development">Razvoj</string>
<string name="label_debug_log">Shrani poročilo o napakah v datoteko</string>
<string name="label_your_bluetooth_scale">Vaša Bluetooth tehtnica</string>
<string name="label_select_measurement">Izberi meritev</string>
<string name="label_select_user">Izberi uporabnika</string>
<string name="label_configure_widget">Konfiguriraj pripomoček(widget)</string>
<string name="label_visceral_fat">Visceralna maščoba</string>
<string name="label_chest">Obseg prsnega koša</string>
<string name="label_thigh">Obseg stegen</string>
<string name="label_biceps">Obseg bicepsa</string>
<string name="label_neck">Obseg vratu</string>
<string name="label_fat_caliper">Kožna guba</string>
<string name="label_caliper1_male">Prsna kožna guba</string>
<string name="label_caliper2_male">Trebušna kožna guba</string>
<string name="label_caliper3_male">Stegenska kožna guba</string>
<string name="label_caliper1_female">Kožna guba tricepsa</string>
<string name="label_caliper2_female">Trebušna kožna guba</string>
<string name="label_caliper3_female">Kožna guba bokov</string>
<string name="label_measure_unit">Enote meritev</string>
<string name="label_activity_level">Stopnja aktivnosti</string>
<string name="activity_level_sedentary">Sedeč</string>
<string name="activity_level_mild">Blag</string>
<string name="activity_level_moderate">Zmeren</string>
<string name="activity_level_heavy">Hud</string>
<string name="activity_level_extreme">Ekstremna</string>
</resources>

View File

@@ -40,8 +40,8 @@
<plurals name="label_days">
<item quantity="other"></item>
</plurals>
<string name="label_last_week">過去7日</string>
<string name="label_last_month">過去30日</string>
<string name="label_last_week">平均過去一週</string>
<string name="label_last_month">平均過去一個月</string>
<string name="label_weight_difference">體重差距</string>
<string name="label_goal_date_is">目標日期:</string>
<string name="label_days_left">剩餘日數</string>
@@ -216,4 +216,5 @@
<string name="label_monthly">每月</string>
<string name="label_development">開發</string>
<string name="label_debug_log">除錯記錄存檔</string>
</resources>
<string name="label_add_measurement">增加測量</string>
</resources>

View File

@@ -6,7 +6,9 @@
<!-- Native names from https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes -->
<item>Catalan (català)</item>
<item>Chinese (traditional; 中文 (繁體))</item>
<item>Croatian (hrvatski jezik)</item>
<item>Czech (čeština)</item>
<item>Danish (dansk)</item>
<item>Dutch (Nederlands)</item>
<item>English</item>
<item>French (français)</item>
@@ -21,6 +23,7 @@
<item>Romanian (Română)</item>
<item>Russian (русский)</item>
<item>Slovak (Slovenčina)</item>
<item>Slovenian (Slovenski Jezik)</item>
<item>Spanish (Español)</item>
<item>Swedish (Svenska)</item>
<item>Turkish (Türkçe)</item>
@@ -31,7 +34,9 @@
<!-- The order below should match the language order above -->
<item>ca</item>
<item>zh-TW</item>
<item>hr</item>
<item>cs</item>
<item>da</item>
<item>nl</item>
<item>en</item>
<item>fr</item>
@@ -46,6 +51,7 @@
<item>ro</item>
<item>ru</item>
<item>sk</item>
<item>sl</item>
<item>es</item>
<item>sv</item>
<item>tr</item>

View File

@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'com.android.tools.build:gradle:3.1.3'
}
}