From bc51ffc5fe6d3840012848dbc315198f16ec70bc Mon Sep 17 00:00:00 2001 From: palachzzz <7zzzzzzzx@gmail.com> Date: Sat, 17 Nov 2018 14:50:56 +0300 Subject: [PATCH 01/34] shutdown fix for drive.google.com --- app/src/main/java/com/cooper/wheellog/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index 6b5c13b2..a6c2f615 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -861,7 +861,7 @@ protected void onDestroy() { mBluetoothLeService = null; } super.onDestroy(); - new CountDownTimer(500, 100) { + new CountDownTimer(60000, 100) { @Override public void onTick(long millisUntilFinished) { From e287fe69cf9d80da91133a58fc49b4a276eec0a1 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Thu, 7 Mar 2019 00:38:23 +0300 Subject: [PATCH 02/34] add KS settings --- gradlew | 72 +++++++++++++--------- gradlew.bat | 174 +++++++++++++++++++++++++--------------------------- 2 files changed, 126 insertions(+), 120 deletions(-) diff --git a/gradlew b/gradlew index 9d82f789..cccdd3d5 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282a..e95643d6 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 63617b24b9d05fda2e2b8e116ea74bf0c891ac8c Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Thu, 7 Mar 2019 12:19:52 +0300 Subject: [PATCH 03/34] Add Speed Alarms settings --- .../cooper/wheellog/PreferencesFragment.java | 12 ++++ .../java/com/cooper/wheellog/WheelData.java | 65 ++++++++++++++++--- app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/preferences_kingsong.xml | 55 ++++++++++++---- 4 files changed, 115 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java index 833c3d18..7b049dca 100644 --- a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java +++ b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java @@ -178,6 +178,18 @@ public void onClick(DialogInterface dialog, int which) { int led_mode = Integer.parseInt(sharedPreferences.getString(getString(R.string.led_mode), "0")); WheelData.getInstance().updateLedMode(led_mode); break; + case "wheel_ks_alarm3": + final int alert3 = sharedPreferences.getInt("wheel_ks_alarm3", 0); + WheelData.getInstance().updateKSAlarm3(alert3); + break; + case "wheel_ks_alarm2": + final int alert2 = sharedPreferences.getInt("wheel_ks_alarm2", 0); + WheelData.getInstance().updateKSAlarm2(alert2); + break; + case "wheel_ks_alarm1": + final int alert1 = sharedPreferences.getInt("wheel_ks_alarm1", 0); + WheelData.getInstance().updateKSAlarm1(alert1); + break; // case "reset_user_trip": // WheelData.getInstance().resetUserDistance(); // break; diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 1d19dccf..7735788f 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -88,6 +88,9 @@ public class WheelData { private int mAlarm1Speed = 0; private int mAlarm2Speed = 0; private int mAlarm3Speed = 0; + private int mKSAlarm1Speed = 0; + private int mKSAlarm2Speed = 0; + private int mKSAlarm3Speed = 0; private int mAlarm1Battery = 0; private int mAlarm2Battery = 0; private int mAlarm3Battery = 0; @@ -348,19 +351,52 @@ public void updateMaxSpeed(int wheelMaxSpeed) { } } if (mWheelType == WHEEL_TYPE.KINGSONG) { - byte[] data = new byte[20]; - data[0] = (byte) 0xAA; - data[1] = (byte) 0x55; - data[6] = (byte) 0x1F; - data[8] = (byte) wheelMaxSpeed; - data[16] = (byte) 0x85; - data[17] = (byte) 0x14; - data[18] = (byte) 0x5A; - data[19] = (byte) 0x5A; - mBluetoothLeService.writeBluetoothGattCharacteristic(data); + if (mWheelMaxSpeed != wheelMaxSpeed) { + mWheelMaxSpeed = wheelMaxSpeed; + updateKSAlarmAndSpeed(); + } } } + + public void updateKSAlarmAndSpeed() { + byte[] data = new byte[20]; + data[0] = (byte) 0xAA; + data[1] = (byte) 0x55; + data[2] = (byte) mKSAlarm1Speed; + data[4] = (byte) mKSAlarm2Speed; + data[6] = (byte) mKSAlarm3Speed; + data[8] = (byte) mWheelMaxSpeed; + data[16] = (byte) 0x85; + data[17] = (byte) 0x14; + data[18] = (byte) 0x5A; + data[19] = (byte) 0x5A; + mBluetoothLeService.writeBluetoothGattCharacteristic(data); + + } + public void updateKSAlarm1(int wheelKSAlarm1) { + if (mKSAlarm1Speed != wheelKSAlarm1) { + mKSAlarm1Speed = wheelKSAlarm1; + updateKSAlarmAndSpeed(); + } + + } + + public void updateKSAlarm2(int wheelKSAlarm2) { + if (mKSAlarm2Speed != wheelKSAlarm2) { + mKSAlarm2Speed = wheelKSAlarm2; + updateKSAlarmAndSpeed(); + } + + } + + public void updateKSAlarm3(int wheelKSAlarm3) { + if (mKSAlarm3Speed != wheelKSAlarm3) { + mKSAlarm3Speed = wheelKSAlarm3; + updateKSAlarmAndSpeed(); + } + + } public void updateSpeakerVolume(int speakerVolume) { if (mWheelSpeakerVolume != speakerVolume) { @@ -863,6 +899,15 @@ private boolean decodeKingSong(byte[] data) { sndata[17] = (byte) 0; mSerialNumber = new String(sndata); } + else if ((data[16] & 255) == 164 || (data[16] & 255) == 181) { //0xa4 || 0xb5 max speed and alerts + mWheelMaxSpeed = (data[10] & 255); + mKSAlarm3Speed = (data[8] & 255); + mKSAlarm2Speed = (data[6] & 255); + mKSAlarm1Speed = (data[4] & 255); + // TODO update SeekBarPreference + // after received 0xa4 send same repeat data[2] =0x01 data[16] = 0x98 + + } } return false; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98d2c83f..1f471626 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,9 @@ reset_user_distance last_mac about + wheel_ks_alarm3 + wheel_ks_alarm2 + wheel_ks_alarm1 + - diff --git a/app/src/main/res/xml/preferences_kingsong.xml b/app/src/main/res/xml/preferences_kingsong.xml index 069bdc51..1f1212e5 100644 --- a/app/src/main/res/xml/preferences_kingsong.xml +++ b/app/src/main/res/xml/preferences_kingsong.xml @@ -1,17 +1,17 @@ - - - + android:summary="On/Off/Auto" /> + + android:summary="On/Off" /> + - - + + + + \ No newline at end of file From 94a51f0e0dc04930cddb7589e2aeedb04b95ddde Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Fri, 8 Mar 2019 01:28:52 +0300 Subject: [PATCH 04/34] add max_speed request --- .../java/com/cooper/wheellog/WheelData.java | 9 ++++++- app/src/main/res/xml/preferences_kingsong.xml | 26 +++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 7735788f..083c9e97 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -18,6 +18,8 @@ import com.cooper.wheellog.utils.NinebotZAdapter; import com.cooper.wheellog.utils.SettingsUtil; +import com.cooper.wheellog.PreferencesFragment; + import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; @@ -368,6 +370,11 @@ public void updateKSAlarmAndSpeed() { data[6] = (byte) mKSAlarm3Speed; data[8] = (byte) mWheelMaxSpeed; data[16] = (byte) 0x85; + + if((mWheelMaxSpeed | mKSAlarm3Speed | mKSAlarm2Speed | mKSAlarm1Speed) == 0){ + data[16] = (byte) 0x98; // request speed & alarm values from wheel + } + data[17] = (byte) 0x14; data[18] = (byte) 0x5A; data[19] = (byte) 0x5A; @@ -904,7 +911,7 @@ else if ((data[16] & 255) == 164 || (data[16] & 255) == 181) { //0xa4 || 0xb5 ma mKSAlarm3Speed = (data[8] & 255); mKSAlarm2Speed = (data[6] & 255); mKSAlarm1Speed = (data[4] & 255); - // TODO update SeekBarPreference + // after received 0xa4 send same repeat data[2] =0x01 data[16] = 0x98 } diff --git a/app/src/main/res/xml/preferences_kingsong.xml b/app/src/main/res/xml/preferences_kingsong.xml index 1f1212e5..04d46335 100644 --- a/app/src/main/res/xml/preferences_kingsong.xml +++ b/app/src/main/res/xml/preferences_kingsong.xml @@ -39,21 +39,21 @@ android:entryValues="@array/pedals_mode_values" android:summary="Soft/Medium/Hard" /> - + android:key="@string/wheel_max_speed" + android:summary="Tiltback speed" + android:title="Wheel speed limit" + sample:msbp_dialogEnabled="true" + sample:msbp_interval="1" + sample:msbp_maxValue="50" + sample:msbp_measurementUnit="km/h" + sample:msbp_minValue="0" /> Date: Fri, 8 Mar 2019 01:56:45 +0300 Subject: [PATCH 05/34] add answer for request --- app/src/main/java/com/cooper/wheellog/WheelData.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 083c9e97..28b18b04 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -18,7 +18,6 @@ import com.cooper.wheellog.utils.NinebotZAdapter; import com.cooper.wheellog.utils.SettingsUtil; -import com.cooper.wheellog.PreferencesFragment; import java.text.SimpleDateFormat; import java.util.*; @@ -913,6 +912,11 @@ else if ((data[16] & 255) == 164 || (data[16] & 255) == 181) { //0xa4 || 0xb5 ma mKSAlarm1Speed = (data[4] & 255); // after received 0xa4 send same repeat data[2] =0x01 data[16] = 0x98 + if((data[16] & 255) == 164) + { + data[16] = (byte)0x98; + mBluetoothLeService.writeBluetoothGattCharacteristic(data); + } } } From 1e1cacbd8ce58281388d7c275edc2f5d49e8b87e Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Fri, 8 Mar 2019 02:10:01 +0300 Subject: [PATCH 06/34] edit settings view --- app/src/main/res/values/strings.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1f471626..dbb12500 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,7 +118,11 @@ gotway_84v gotway_voltage no_settings - + wheel_ks_alarm3 + wheel_ks_alarm2 + wheel_ks_alarm1 + + // LOG PREFERENCES auto_log @@ -140,9 +144,6 @@ reset_user_distance last_mac about - wheel_ks_alarm3 - wheel_ks_alarm2 - wheel_ks_alarm1 From bd371086a627e51ef2a400b0fb0e5186dac8865d Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Sat, 9 Mar 2019 02:27:56 +0300 Subject: [PATCH 07/34] load settings speed&alerts --- .../cooper/wheellog/PreferencesFragment.java | 12 +++++++- .../java/com/cooper/wheellog/WheelData.java | 30 ++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java index 7b049dca..28e53aa0 100644 --- a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java +++ b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java @@ -288,7 +288,17 @@ public boolean onPreferenceClick(Preference preference) { getPreferenceScreen().removeAll(); if (mWheelType == WHEEL_TYPE.NINEBOT_Z) addPreferencesFromResource(R.xml.preferences_ninebot_z); if (mWheelType == WHEEL_TYPE.INMOTION) addPreferencesFromResource(R.xml.preferences_inmotion); - if (mWheelType == WHEEL_TYPE.KINGSONG) addPreferencesFromResource(R.xml.preferences_kingsong); + if (mWheelType == WHEEL_TYPE.KINGSONG) { + addPreferencesFromResource(R.xml.preferences_kingsong); + if(WheelData.getInstance().is_pref_received()) { + + correctWheelBarState(getString(R.string.wheel_max_speed), WheelData.getInstance().getWheelMaxSpeed()); + correctWheelBarState(getString(R.string.wheel_ks_alarm1), WheelData.getInstance().getKSAlarm1Speed()); + correctWheelBarState(getString(R.string.wheel_ks_alarm2), WheelData.getInstance().getKSAlarm2Speed()); + correctWheelBarState(getString(R.string.wheel_ks_alarm3), WheelData.getInstance().getKSAlarm3Speed()); + + } + } if (mWheelType == WHEEL_TYPE.GOTWAY) { //if (mWheelType == WHEEL_TYPE.INMOTION) { addPreferencesFromResource(R.xml.preferences_gotway); diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 28b18b04..4d54d52f 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -63,6 +63,7 @@ public class WheelData { private int mFanStatus; private boolean mConnectionState = false; private boolean mNewWheelSettings = false; + private boolean mKSAlertsAndSpeedupdated = false; private String mName = "Unknown"; private String mModel = "Unknown"; private String mModeStr = "Unknown"; @@ -80,7 +81,7 @@ public class WheelData { private boolean mWheelLightEnabled = false; private boolean mWheelLedEnabled = false; private boolean mWheelButtonDisabled = false; - private int mWheelMaxSpeed = 25; + private int mWheelMaxSpeed = 0; private int mWheelSpeakerVolume = 50; private int mWheelTiltHorizon = 0; @@ -153,9 +154,25 @@ boolean getWheelHandleButton() { } int getWheelMaxSpeed() { + return mWheelMaxSpeed; } - + + int getKSAlarm1Speed() { + + return mKSAlarm1Speed; + } + + int getKSAlarm2Speed() { + + return mKSAlarm2Speed; + } + + int getKSAlarm3Speed() { + + return mKSAlarm3Speed; + } + int getSpeakerVolume() { return mWheelSpeakerVolume; } @@ -164,6 +181,10 @@ int getPedalsPosition() { return mWheelTiltHorizon; } + public boolean is_pref_received(){ + return mKSAlertsAndSpeedupdated; + } + public void setBtName(String btName) { mBtName = btName; } @@ -904,13 +925,14 @@ private boolean decodeKingSong(byte[] data) { System.arraycopy(data, 17, sndata, 14, 3); sndata[17] = (byte) 0; mSerialNumber = new String(sndata); + updateKSAlarmAndSpeed(); } else if ((data[16] & 255) == 164 || (data[16] & 255) == 181) { //0xa4 || 0xb5 max speed and alerts mWheelMaxSpeed = (data[10] & 255); mKSAlarm3Speed = (data[8] & 255); mKSAlarm2Speed = (data[6] & 255); mKSAlarm1Speed = (data[4] & 255); - + mKSAlertsAndSpeedupdated = true; // after received 0xa4 send same repeat data[2] =0x01 data[16] = 0x98 if((data[16] & 255) == 164) { @@ -1118,7 +1140,7 @@ void reset() { mWheelLightEnabled = false; mWheelLedEnabled = false; mWheelButtonDisabled = false; - mWheelMaxSpeed = 25; + mWheelMaxSpeed = 0; mWheelSpeakerVolume = 50; } From cfca1180947f1c10db9774dfd6e5e4fe972dc532 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Thu, 14 Mar 2019 23:40:34 +0300 Subject: [PATCH 08/34] started work on localization --- .../com/cooper/wheellog/MainActivity.java | 32 ++++++------- .../com/cooper/wheellog/views/WheelView.java | 8 ++-- app/src/main/res/values-ru-rRU/strings.xml | 45 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/preferences_kingsong.xml | 8 ++-- 5 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/values-ru-rRU/strings.xml diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index 6b5c13b2..60fa967e 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -568,23 +568,23 @@ private void updateScreen(boolean updateGraph) { break; case 1: // Text View if (use_mph) { - tvSpeed.setText(String.format(Locale.US, "%.1f mph", kmToMiles(WheelData.getInstance().getSpeedDouble()))); - tvTopSpeed.setText(String.format(Locale.US, "%.1f mph", kmToMiles(WheelData.getInstance().getTopSpeedDouble()))); - tvAverageSpeed.setText(String.format(Locale.US, "%.1f mph", kmToMiles(WheelData.getInstance().getAverageSpeedDouble()))); - tvAverageRidingSpeed.setText(String.format(Locale.US, "%.1f mph", kmToMiles(WheelData.getInstance().getAverageRidingSpeedDouble()))); - tvDistance.setText(String.format(Locale.US, "%.2f mi", kmToMiles(WheelData.getInstance().getDistanceDouble()))); - tvWheelDistance.setText(String.format(Locale.US, "%.2f mi", kmToMiles(WheelData.getInstance().getWheelDistanceDouble()))); - tvUserDistance.setText(String.format(Locale.US, "%.2f mi", kmToMiles(WheelData.getInstance().getUserDistanceDouble()))); - tvTotalDistance.setText(String.format(Locale.US, "%.2f mi", kmToMiles(WheelData.getInstance().getTotalDistanceDouble()))); + tvSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.mph), kmToMiles(WheelData.getInstance().getSpeedDouble()))); + tvTopSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.mph), kmToMiles(WheelData.getInstance().getTopSpeedDouble()))); + tvAverageSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.mph), kmToMiles(WheelData.getInstance().getAverageSpeedDouble()))); + tvAverageRidingSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.mph), kmToMiles(WheelData.getInstance().getAverageRidingSpeedDouble()))); + tvDistance.setText(String.format(Locale.US, "%.2f " + getString(R.string.milli), kmToMiles(WheelData.getInstance().getDistanceDouble()))); + tvWheelDistance.setText(String.format(Locale.US, "%.2f " + getString(R.string.milli), kmToMiles(WheelData.getInstance().getWheelDistanceDouble()))); + tvUserDistance.setText(String.format(Locale.US, "%.2f " + getString(R.string.milli), kmToMiles(WheelData.getInstance().getUserDistanceDouble()))); + tvTotalDistance.setText(String.format(Locale.US, "%.2f " + getString(R.string.milli), kmToMiles(WheelData.getInstance().getTotalDistanceDouble()))); } else { - tvSpeed.setText(String.format(Locale.US, "%.1f km/h", WheelData.getInstance().getSpeedDouble())); - tvTopSpeed.setText(String.format(Locale.US, "%.1f km/h", WheelData.getInstance().getTopSpeedDouble())); - tvAverageSpeed.setText(String.format(Locale.US, "%.1f km/h", WheelData.getInstance().getAverageSpeedDouble())); - tvAverageRidingSpeed.setText(String.format(Locale.US, "%.1f km/h", WheelData.getInstance().getAverageRidingSpeedDouble())); - tvDistance.setText(String.format(Locale.US, "%.3f km", WheelData.getInstance().getDistanceDouble())); - tvWheelDistance.setText(String.format(Locale.US, "%.3f km", WheelData.getInstance().getWheelDistanceDouble())); - tvUserDistance.setText(String.format(Locale.US, "%.3f km", WheelData.getInstance().getUserDistanceDouble())); - tvTotalDistance.setText(String.format(Locale.US, "%.3f km", WheelData.getInstance().getTotalDistanceDouble())); + tvSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.kmh), WheelData.getInstance().getSpeedDouble())); + tvTopSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.kmh), WheelData.getInstance().getTopSpeedDouble())); + tvAverageSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.kmh), WheelData.getInstance().getAverageSpeedDouble())); + tvAverageRidingSpeed.setText(String.format(Locale.US, "%.1f " + getString(R.string.kmh), WheelData.getInstance().getAverageRidingSpeedDouble())); + tvDistance.setText(String.format(Locale.US, "%.3f " + getString(R.string.km), WheelData.getInstance().getDistanceDouble())); + tvWheelDistance.setText(String.format(Locale.US, "%.3f " + getString(R.string.km), WheelData.getInstance().getWheelDistanceDouble())); + tvUserDistance.setText(String.format(Locale.US, "%.3f " + getString(R.string.km), WheelData.getInstance().getUserDistanceDouble())); + tvTotalDistance.setText(String.format(Locale.US, "%.3f " + getString(R.string.km), WheelData.getInstance().getTotalDistanceDouble())); } tvVoltage.setText(String.format(Locale.US, "%.2fV", WheelData.getInstance().getVoltageDouble())); diff --git a/app/src/main/java/com/cooper/wheellog/views/WheelView.java b/app/src/main/java/com/cooper/wheellog/views/WheelView.java index 15ef9240..491ffa4e 100644 --- a/app/src/main/java/com/cooper/wheellog/views/WheelView.java +++ b/app/src/main/java/com/cooper/wheellog/views/WheelView.java @@ -293,7 +293,7 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { Math.round(center_x + (speedTextKPHRectSize/2)), Math.round(center_y + (speedTextKPHRectSize/2))); - speedTextKPHSize = calculateFontSize(boundaryOfText, speedTextKPHRect, "km/h", textPaint); + speedTextKPHSize = calculateFontSize(boundaryOfText, speedTextKPHRect, getResources().getString(R.string.kmh), textPaint); speedTextKPHHeight = boundaryOfText.height(); @@ -437,7 +437,7 @@ protected void onDraw(Canvas canvas) { canvas.drawText(speedString, outerArcRect.centerX(), speedTextRect.centerY()+(speedTextRect.height()/2), textPaint); textPaint.setTextSize(speedTextKPHSize); textPaint.setColor(getContext().getResources().getColor(R.color.wheelview_text)); - String metric = mUseMPH ? "mph" : "km/h"; + String metric = mUseMPH ? getResources().getString(R.string.mph) : getResources().getString(R.string.kmh); canvas.drawText(metric, outerArcRect.centerX(),speedTextRect.bottom+(speedTextKPHHeight*1.25F), textPaint); //#################################################### @@ -500,8 +500,8 @@ protected void onDraw(Canvas canvas) { canvas.drawText(String.format(Locale.US, "%.0f mi", kmToMiles(mTotalDistance)), brRect.centerX(), brRect.centerY() + boxTextHeight, textPaint); canvas.drawText(String.format(Locale.US, "%.1f mph", kmToMiles(mAverageSpeed)), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); } else { - canvas.drawText(String.format(Locale.US, "%.1f km/h", mTopSpeed), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); - canvas.drawText(String.format(Locale.US, "%.1f km/h", mAverageSpeed), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mTopSpeed), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mAverageSpeed), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); if (mDistance < 1) canvas.drawText(String.format(Locale.US, "%.0f m", mDistance * 1000), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); else diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml new file mode 100644 index 00000000..85d61f98 --- /dev/null +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -0,0 +1,45 @@ + + + WheelLog + Предупреждение 1 + Предупреждение 2 + Предупреждение 3 + Подъем педалей + Поиск + Остановить + Подключить + Подключение + Макс. скорость + Батарея + Дистанция + Поездка + Мой пробег + Время работы + Время движения + Напряжение + Ток + Мощность + Температура + Пройдено + Сред. скорость + Сред. движения + Темп. процессора + Полный пробег + Название + Модель + Версия + Серийный номер + Ожидание колеса + Вентилятор + Скорость + Режим работы + Модель колеса определена %1$s + Начало записи в %1$s + Поиск... + Повторно нажмите назад для выхода + км/ч + миль/ч + км + милли + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dbb12500..aa72a701 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,6 +144,10 @@ reset_user_distance last_mac about + km/h + mph + km + mi diff --git a/app/src/main/res/xml/preferences_kingsong.xml b/app/src/main/res/xml/preferences_kingsong.xml index 04d46335..b86b8fc0 100644 --- a/app/src/main/res/xml/preferences_kingsong.xml +++ b/app/src/main/res/xml/preferences_kingsong.xml @@ -44,7 +44,7 @@ android:enabled="true" android:key="@string/wheel_max_speed" android:summary="Tiltback speed" - android:title="Wheel speed limit" + android:title="@string/max_speed_ln" sample:msbp_dialogEnabled="true" sample:msbp_interval="1" sample:msbp_maxValue="50" @@ -55,7 +55,7 @@ android:enabled="true" android:key="@string/wheel_ks_alarm3" android:summary="Alarm 3 speed" - android:title="Wheel alarm3 speed" + android:title="@string/alert3_ln" sample:msbp_dialogEnabled="true" sample:msbp_interval="1" sample:msbp_maxValue="50" @@ -66,7 +66,7 @@ android:enabled="true" android:key="@string/wheel_ks_alarm2" android:summary="Alarm 2 speed" - android:title="Wheel alarm2 speed" + android:title="@string/alert2_ln" sample:msbp_dialogEnabled="true" sample:msbp_interval="1" sample:msbp_maxValue="50" @@ -77,7 +77,7 @@ android:enabled="true" android:key="@string/wheel_ks_alarm1" android:summary="Alarm 1 speed" - android:title="Wheel alarm1 speed" + android:title="@string/alert1_ln" sample:msbp_dialogEnabled="true" sample:msbp_interval="1" sample:msbp_maxValue="50" From 80e118d720529aec585c0b1f551d2a5acadee5e8 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Fri, 15 Mar 2019 01:24:15 +0300 Subject: [PATCH 09/34] mid point commit --- app/src/main/res/values-ru-rRU/strings.xml | 31 +++++++++++++-- app/src/main/res/values/arrays.xml | 32 ++++++++-------- app/src/main/res/values/strings.xml | 27 +++++++++++++ app/src/main/res/xml/preferences_kingsong.xml | 38 +++++++++---------- app/src/main/res/xml/preferences_watch.xml | 6 +-- 5 files changed, 92 insertions(+), 42 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 85d61f98..13c4ba12 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -1,10 +1,10 @@ WheelLog - Предупреждение 1 - Предупреждение 2 - Предупреждение 3 - Подъем педалей + Первое предупреждение + Второе предупреждение + Третье предупреждение + Подъем педалей Поиск Остановить Подключить @@ -41,5 +41,28 @@ миль/ч км милли + Настройка Сигнала + Скорость подъема педалей + 4 звуковых сигнала + 3 звуковых сигнала + 2 звуковых сигнала + Подать звуковой сингнал при нажатии на кнопку часов Pebble + Настройка педалей + Режим светодиодов + Режим стробоскопа + Режим фары + Вкл./Выкл. + Вкл./Выкл./Авто + Вкл. + Выкл. + Авто + Строб + Отключено + Включено + Жесткие + Средние + Мягкие + Встроенный (KingSong) + через блютуз \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 97804884..f962889d 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -6,9 +6,9 @@ - Disabled - On-board (KingSong) - via bluetooth audio + @string/disabled + @string/on_board_horn_ks + @string/bluetooth_audio_horn @@ -18,9 +18,9 @@ - Hard - Medium - Soft + @string/hard + @string/medium + @string/soft @@ -30,16 +30,16 @@ - On - Off - Auto + @string/on + @string/off + @string/auto - Off - On - Strobe + @string/off + @string/on + @string/strobe @@ -61,8 +61,8 @@ - Off - On + @string/off + @string/on @@ -71,8 +71,8 @@ - Off - On + @string/off + @string/on diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa72a701..e490e528 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -148,6 +148,33 @@ mph km mi + Horn Mode + Executed when the select button on the Pebble watch is pressed + Tiltback speed + Wheel speed limit + 1st Alert Speed + 2nd Alert Speed + 3rd Alert Speed + 4 beeps alarm + 3 beeps alarm + 2 beeps alarm + Pedals Mode + Leds mode + Strobe Mode + Light Mode + On/Off + On/Off/Auto + On + Off + Auto + Strobe + Disabled + Enabled + Hard + Medium + Soft + On-board (KingSong) + via bluetooth audio diff --git a/app/src/main/res/xml/preferences_kingsong.xml b/app/src/main/res/xml/preferences_kingsong.xml index b86b8fc0..faae45f5 100644 --- a/app/src/main/res/xml/preferences_kingsong.xml +++ b/app/src/main/res/xml/preferences_kingsong.xml @@ -5,35 +5,35 @@ + android:summary="@string/on_off_auto" /> + android:summary="@string/on_off" /> + android:summary="@string/on_off" /> \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_watch.xml b/app/src/main/res/xml/preferences_watch.xml index d4a22a3e..0749c5a7 100644 --- a/app/src/main/res/xml/preferences_watch.xml +++ b/app/src/main/res/xml/preferences_watch.xml @@ -3,11 +3,11 @@ xmlns:android="http://schemas.android.com/apk/res/android"> + android:key="@string/horn_mode" + android:summary="@string/horn_mode_description" + android:title="@string/horn_mode_title" /> \ No newline at end of file From 8aee3e23b8f9d2d9dbd84b85e560a38617136258 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Fri, 15 Mar 2019 16:22:01 +0300 Subject: [PATCH 10/34] finish KingSong localization --- app/build.gradle | 4 +- app/release/output.json | 2 +- .../com/cooper/wheellog/MainActivity.java | 9 +-- .../cooper/wheellog/PreferencesFragment.java | 34 +++++------ .../com/cooper/wheellog/ScanActivity.java | 6 +- .../com/cooper/wheellog/views/WheelView.java | 14 ++--- app/src/main/res/layout/main_view_three.xml | 4 +- app/src/main/res/values-ru-rRU/strings.xml | 52 +++++++++++++++-- app/src/main/res/values/strings.xml | 48 ++++++++++++++- app/src/main/res/xml/preferences.xml | 18 +++--- app/src/main/res/xml/preferences_alarms.xml | 58 +++++++++---------- app/src/main/res/xml/preferences_logs.xml | 16 ++--- app/src/main/res/xml/preferences_speed.xml | 10 ++-- 13 files changed, 179 insertions(+), 96 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 50cc0c72..acc12702 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.cooper.wheellog" minSdkVersion 18 targetSdkVersion 26 - versionCode 36 - versionName "2.0.19" + versionCode 37 + versionName "2.0.19_KS_RE" buildConfigField 'String', 'BUILD_TIME', 'new java.text.SimpleDateFormat("HH:mm dd.MM.yyyy", java.util.Locale.US).format(new java.util.Date(' + System.currentTimeMillis() +'L))' } buildTypes { diff --git a/app/release/output.json b/app/release/output.json index e0817289..3d2f6b38 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":36,"versionName":"2.0.19","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"2.0.19_KS_RE","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index 60fa967e..e1767727 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -614,8 +614,8 @@ private void updateScreen(boolean updateGraph) { LineDataSet dataSetCurrent; if (chart1.getData() == null) { - dataSetSpeed = new LineDataSet(null, "speed"); - dataSetCurrent = new LineDataSet(null, "current"); + dataSetSpeed = new LineDataSet(null, getString(R.string.speed_axis)); + dataSetCurrent = new LineDataSet(null, getString(R.string.current_axis)); dataSetSpeed.setLineWidth(2); dataSetCurrent.setLineWidth(2); dataSetSpeed.setAxisDependency(YAxis.AxisDependency.LEFT); @@ -635,8 +635,8 @@ private void updateScreen(boolean updateGraph) { findViewById(R.id.leftAxisLabel).setVisibility(View.VISIBLE); findViewById(R.id.rightAxisLabel).setVisibility(View.VISIBLE); } else { - dataSetSpeed = (LineDataSet) chart1.getData().getDataSetByLabel("speed", true); - dataSetCurrent = (LineDataSet) chart1.getData().getDataSetByLabel("current", true); + dataSetSpeed = (LineDataSet) chart1.getData().getDataSetByLabel(getString(R.string.speed_axis), true); + dataSetCurrent = (LineDataSet) chart1.getData().getDataSetByLabel(getString(R.string.current_axis), true); } dataSetSpeed.clear(); @@ -765,6 +765,7 @@ public void onDrawerStateChanged(int newState) { chart1.setHighlightPerTapEnabled(false); chart1.setHighlightPerDragEnabled(false); chart1.getLegend().setTextColor(getResources().getColor(android.R.color.white)); + chart1.setNoDataText(getString(R.string.no_chart_data)); chart1.setNoDataTextColor(getResources().getColor(android.R.color.white)); YAxis leftAxis = chart1.getAxisLeft(); diff --git a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java index 28e53aa0..2e632496 100644 --- a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java +++ b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java @@ -13,18 +13,12 @@ import android.view.View; import android.widget.*; import android.text.InputType; -import android.text.TextUtils; import com.cooper.wheellog.utils.Constants; import com.cooper.wheellog.utils.Constants.WHEEL_TYPE; import com.cooper.wheellog.utils.SettingsUtil; -import com.cooper.wheellog.BuildConfig; import com.pavelsikun.seekbarpreference.SeekBarPreference; - - -import timber.log.Timber; - public class PreferencesFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { enum SettingsScreen { @@ -219,7 +213,7 @@ public void onClick(View view) { switch (currentScreen) { case Main: - tb.setTitle("Settings"); + tb.setTitle(getText(R.string.settings_title)); Preference speed_button = findPreference(getString(R.string.speed_preferences)); Preference logs_button = findPreference(getString(R.string.log_preferences)); Preference alarm_button = findPreference(getString(R.string.alarm_preferences)); @@ -345,9 +339,9 @@ public boolean onPreferenceClick(Preference preference) { String versionName = BuildConfig.VERSION_NAME; String buildTime = BuildConfig.BUILD_TIME; new AlertDialog.Builder(getActivity()) - .setTitle("About WheelLog") + .setTitle(R.string.about_app_title) .setMessage(Html.fromHtml(String.format("Version %s
build at %s
by Palachzzz
palachzzz.wl@gmail.com", versionName, buildTime))) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { } @@ -366,24 +360,24 @@ public void onClick(DialogInterface dialog, int which) { @Override public boolean onPreferenceClick(Preference preference) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle("MAC Edit"); + builder.setTitle(getText(R.string.edit_mac_addr_title)); final EditText input = new EditText(getActivity()); input.setInputType(InputType.TYPE_CLASS_TEXT); input.setText(SettingsUtil.getLastAddress(getActivity())); builder.setView(input); - builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final String deviceAddress = input.getText().toString(); SettingsUtil.setLastAddress(getActivity(), deviceAddress); AlertDialog.Builder builder1 = new AlertDialog.Builder(getActivity()); - builder1.setTitle("Wheel Password ( InMotion only )"); + builder1.setTitle(getText(R.string.wheel_pass_imotion)); final EditText input1 = new EditText(getActivity()); input1.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); builder1.setView(input1); - builder1.setPositiveButton("OK", new DialogInterface.OnClickListener() { + builder1.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String password = input1.getText().toString(); @@ -396,7 +390,7 @@ public void onClick(DialogInterface dialog, int which) { //finish(); } }); - builder1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + builder1.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); @@ -411,7 +405,7 @@ public void onClick(DialogInterface dialog, int which) { //finish(); } }); - builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); @@ -429,20 +423,20 @@ public void onClick(DialogInterface dialog, int which) { break; case Speed: - tb.setTitle("Speed Settings"); + tb.setTitle(getText(R.string.speed_settings_title)); break; case Logs: - tb.setTitle("Log Settings"); + tb.setTitle(getText(R.string.logs_settings_title)); break; case Alarms: - tb.setTitle("Alarm Settings"); + tb.setTitle(getText(R.string.alarm_settings_title)); hideShowSeekBars(); break; case Watch: - tb.setTitle("Watch Settings"); + tb.setTitle(getText(R.string.watch_settings_title)); break; case Wheel: - tb.setTitle("Wheel Settings"); + tb.setTitle(getText(R.string.wheel_settings_title)); //getActivity().sendBroadcast(new Intent(Constants.ACTION_WHEEL_SETTING_CHANGED).putExtra(Constants.INTENT_EXTRA_WHEEL_REFRESH, true)); break; } diff --git a/app/src/main/java/com/cooper/wheellog/ScanActivity.java b/app/src/main/java/com/cooper/wheellog/ScanActivity.java index db6c8563..5efde550 100644 --- a/app/src/main/java/com/cooper/wheellog/ScanActivity.java +++ b/app/src/main/java/com/cooper/wheellog/ScanActivity.java @@ -103,12 +103,12 @@ public void onItemClick(AdapterView adapterView, final View view, int i, long setResult(RESULT_OK, intent); //Ask for inmotion password AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext()); - builder.setTitle("Wheel Password ( InMotion only )"); + builder.setTitle(R.string.wheel_pass_imotion); final EditText input = new EditText(view.getContext()); input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); builder.setView(input); - builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String password = input.getText().toString(); @@ -116,7 +116,7 @@ public void onClick(DialogInterface dialog, int which) { finish(); } }); - builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); diff --git a/app/src/main/java/com/cooper/wheellog/views/WheelView.java b/app/src/main/java/com/cooper/wheellog/views/WheelView.java index 491ffa4e..6d9ffa84 100644 --- a/app/src/main/java/com/cooper/wheellog/views/WheelView.java +++ b/app/src/main/java/com/cooper/wheellog/views/WheelView.java @@ -495,19 +495,19 @@ protected void onDraw(Canvas canvas) { canvas.drawText(mCurrentTime, mlRect.centerX(), mlRect.centerY() + boxTextHeight + (box_inner_padding / 2), textPaint); if (mUseMPH) { - canvas.drawText(String.format(Locale.US, "%.1f mph", kmToMiles(mTopSpeed)), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); - canvas.drawText(String.format(Locale.US, "%.2f mi", kmToMiles(mDistance)), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); - canvas.drawText(String.format(Locale.US, "%.0f mi", kmToMiles(mTotalDistance)), brRect.centerX(), brRect.centerY() + boxTextHeight, textPaint); - canvas.drawText(String.format(Locale.US, "%.1f mph", kmToMiles(mAverageSpeed)), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.mph), kmToMiles(mTopSpeed)), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.2f " + getResources().getString(R.string.milli), kmToMiles(mDistance)), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.0f " + getResources().getString(R.string.milli), kmToMiles(mTotalDistance)), brRect.centerX(), brRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.mph), kmToMiles(mAverageSpeed)), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); } else { canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mTopSpeed), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mAverageSpeed), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); if (mDistance < 1) - canvas.drawText(String.format(Locale.US, "%.0f m", mDistance * 1000), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.0f " + getResources().getString(R.string.km), mDistance * 1000), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); else - canvas.drawText(String.format(Locale.US, "%.2f km", mDistance), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.2f " + getResources().getString(R.string.km), mDistance), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); - canvas.drawText(String.format(Locale.US, "%.0f km", mTotalDistance), brRect.centerX(), brRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.0f " + getResources().getString(R.string.km), mTotalDistance), brRect.centerX(), brRect.centerY() + boxTextHeight, textPaint); } } diff --git a/app/src/main/res/layout/main_view_three.xml b/app/src/main/res/layout/main_view_three.xml index a42fdf72..f83abe4c 100644 --- a/app/src/main/res/layout/main_view_three.xml +++ b/app/src/main/res/layout/main_view_three.xml @@ -14,7 +14,7 @@ android:layout_marginLeft="5dp" android:layout_alignParentTop="true" android:textColor="@android:color/white" - android:text="km/h"/> + android:text="@string/kmh"/> + android:text="@string/amp"/> Поездка Мой пробег Время работы - Время движения + Время в пути Напряжение Ток Мощность @@ -46,8 +46,8 @@ 4 звуковых сигнала 3 звуковых сигнала 2 звуковых сигнала - Подать звуковой сингнал при нажатии на кнопку часов Pebble - Настройка педалей + Подавать звуковой сингнал при нажатии кнопки на часах Pebble. + Настройки педалей Режим светодиодов Режим стробоскопа Режим фары @@ -64,5 +64,49 @@ Мягкие Встроенный (KingSong) через блютуз - + Настройки скорости + Настройки логирования + Настройки сигналов + Настройки часов + Настройки колеса + Обнулить максимальную скорость + Обнулить мой пробег + Изменить MAC адрес + О программе Wheellog + Настройки + Пароль (только для Inmotion) + А + Максимальная скорость + Ограничение спидометра на главном экране. + Использовать милли + Показывать милли вместо киллометров. + Включить предупреждения + Телефон будет вибрировать во время предупреждения. + Отключить вибросигнал + Телефон не будет вибрировать, но часы Pebble будут показывать предупреждения. + Превышение скорости 1 + Предупреждение о достигнутой скорости + Превышение скорости 2 + Превышение скорости 3 + Уровень разряда 1 + Уровень предупреждения о разряде батареи. + Уровень разряда 3 + Уровень разряда 2 + Предупреждение по току + Ток + Предупреждение о превышении заданного тока. + Предупреждение о температуре + Температура + Уровень срабатывания предупреждения о температуре. + скорость + ток + Нет данных для отображения графика + Автоматическое логирование + Автоматически начать запись логов при подключении колеса. + Выгрузка логов + Автоматически выгружать логи на Google Drive. + Местоположение + Добавить текущую позицию в лог. + Использование GPS + Использовать данные GPS для точного определения местоположения. При использовании GPS повышается энергопотребление телефона, если параметр отключен используются даенные сотовой связи. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e490e528..3d526792 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,7 @@ Model Version Serial Number - %1$d%% • %2$d°C • %3$.1f km + %1$d%% • %2$d°C • %3$.1f km Watch Icon Logging Icon Connection Icon @@ -175,6 +175,50 @@ Soft On-board (KingSong) via bluetooth audio - + Speed Settings + Log Settings + Alarm Settings + Watch Preferences + Wheel Settings + Reset top speed + Reset user distance + Edit MAC address + About WheelLog + Settings + Wheel Password ( InMotion only ) + A + Max Speed + The maximum speed shown on the outer dial + Use MPH + Show speed in miles rather than kilometers + Enable Alarms + Allow the phone to vibrate as a warning + Disable Phone Vibration + Phone will not vibrate but the alarm will be passed to a connected Pebble Watch + Speed Alarm 1 + Speed that triggers the alarm + Speed Alarm 2 + Speed Alarm 3 + Battery Percent + Battery percent that activates the alarm + Battery Percent + Battery Percent + Current Alarm + Current + Current that triggers the alarm + Temperature Alarm + Temperature + Temperature that triggers the alarm + speed + current + No data available + Auto Log + Start logging automatically when a wheel is connected + Auto Upload Logs + Automatically upload log files to Google Drive + Log location + Include location data within the logs + Use GPS for location + Use GPS for location rather than network provider. GPS is more accurate but may consume more battery power diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 5ce3233c..26042a9e 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -4,47 +4,47 @@ + android:title="@string/reset_top_spped_title" /> + android:title="@string/reset_user_distance_title" /> + android:title="@string/edit_mac_addr_title" /> + android:title="@string/about_app_title" /> \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_alarms.xml b/app/src/main/res/xml/preferences_alarms.xml index 6326d9a5..48e2c921 100644 --- a/app/src/main/res/xml/preferences_alarms.xml +++ b/app/src/main/res/xml/preferences_alarms.xml @@ -5,34 +5,34 @@ + android:title="@string/enable_alarms_title" + android:summary="@string/enable_alarms_description" /> + android:title="@string/disable_phone_vibrate_title" + android:summary="@string/disable_phone_vibration_description" /> + android:title="@string/speed_alarm1_phone_title"> + android:title="@string/speed_alarm2_phone_title"> + android:title="@string/speed_alarm3_phone_title"> + android:title="@string/current_alarm_title"> + android:title="@string/temperature_alarm_title"> + android:title="@string/auto_log_title" + android:summary="@string/auto_log_description" /> + android:title="@string/auto_upload_log_title" + android:summary="@string/auto_upload_log_description" /> + android:title="@string/log_location_title" + android:summary="@string/log_location_decription" /> + android:title="@string/use_gps_title" + android:summary="@string/use_gps_description" /> \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_speed.xml b/app/src/main/res/xml/preferences_speed.xml index cd20e099..a5459c66 100644 --- a/app/src/main/res/xml/preferences_speed.xml +++ b/app/src/main/res/xml/preferences_speed.xml @@ -5,19 +5,19 @@ + android:title="@string/use_mph_title" + android:summary="@string/use_mph_description" /> \ No newline at end of file From 723fa593b9078a2bd599aad98ff77ba1b9abf181 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Tue, 19 Mar 2019 10:42:51 +0300 Subject: [PATCH 11/34] finish gotway localization --- app/src/main/res/values-ru-rRU/strings.xml | 12 +++++++ app/src/main/res/values/arrays.xml | 6 ++-- app/src/main/res/values/strings.xml | 12 +++++++ app/src/main/res/xml/preferences_gotway.xml | 34 +++++++++---------- app/src/main/res/xml/preferences_kingsong.xml | 2 +- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 3a77ab99..f1ea3ebb 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -109,4 +109,16 @@ Добавить текущую позицию в лог. Использование GPS Использовать данные GPS для точного определения местоположения. При использовании GPS повышается энергопотребление телефона, если параметр отключен используются даенные сотовой связи. + Выкл./Вкл./Строб + Мягкие/Средние/Жесткие + Режим предупреждения + Настроить передупреждения. + Калибровка + У меня Gotway MCM + Если у вас Gotway MCM, а скорость растояние и другие параметры отображаются некорректно, тогда попробуйте установить тут галочку. + Напряжение батареи + 67В/84В/100В + Отключить первое предупреждение. + Отключить второе предупреждение. + Включить предупреждения \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index f962889d..c88c8444 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -43,9 +43,9 @@
- Turn off level 1 alarm - Turn off level 2 alarm - Turn on alarm + @string/off_level_1_alarm + @string/off_level_2_alarm + @string/on_level_alarm diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d526792..25399f29 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -220,5 +220,17 @@ Include location data within the logs Use GPS for location Use GPS for location rather than network provider. GPS is more accurate but may consume more battery power + Off/On/Strobe + Soft/Medium/Hard + Alarm settings + Alarm Mode + Calibration + My Wheel is a Gotway MCM + If your wheel is Gotway MCM, and it shows wrong speed, distance and others parameters, it should make them normal + Battery voltage + 67V/84V/100V + Turn off level 1 alarm + Turn off level 2 alarm + Turn on alarm diff --git a/app/src/main/res/xml/preferences_gotway.xml b/app/src/main/res/xml/preferences_gotway.xml index da36ec6d..4b256106 100644 --- a/app/src/main/res/xml/preferences_gotway.xml +++ b/app/src/main/res/xml/preferences_gotway.xml @@ -5,63 +5,63 @@ + android:summary="@string/on_off_strobe" /> + android:summary="@string/alarm_settings_title" /> + android:summary="@string/soft_medium_hard" /> + android:title="@string/calibration_title"/> + android:title="@string/is_gotway_mcm_title" + android:summary="@string/is_gotway_mcm_description" /> + android:entryValues="@array/gotway_voltage" + android:key="@string/gotway_voltage" + android:summary="@string/battary_voltage_description" + android:title="@string/battery_voltage_title" /> \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_kingsong.xml b/app/src/main/res/xml/preferences_kingsong.xml index faae45f5..f3692156 100644 --- a/app/src/main/res/xml/preferences_kingsong.xml +++ b/app/src/main/res/xml/preferences_kingsong.xml @@ -37,7 +37,7 @@ android:defaultValue="1" android:entries="@array/pedals_mode" android:entryValues="@array/pedals_mode_values" - android:summary="Soft/Medium/Hard" /> + android:summary="@string/soft_medium_hard" /> Date: Tue, 19 Mar 2019 10:54:09 +0300 Subject: [PATCH 12/34] samll fix --- app/src/main/java/com/cooper/wheellog/MainActivity.java | 2 +- app/src/main/java/com/cooper/wheellog/views/WheelView.java | 2 +- app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index e1767727..2c5be937 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -595,7 +595,7 @@ private void updateScreen(boolean updateGraph) { tvCurrent.setText(String.format(Locale.US, "%.2fA", WheelData.getInstance().getCurrentDouble())); tvPower.setText(String.format(Locale.US, "%.2fW", WheelData.getInstance().getPowerDouble())); tvBattery.setText(String.format(Locale.US, "%d%%", WheelData.getInstance().getBatteryLevel())); - tvFanStatus.setText(WheelData.getInstance().getFanStatus() == 0 ? "Off" : "On"); + tvFanStatus.setText(WheelData.getInstance().getFanStatus() == 0 ? getString(R.string.off) : getString(R.string.on)); tvVersion.setText(String.format(Locale.US, "%s", WheelData.getInstance().getVersion())); tvName.setText(WheelData.getInstance().getName()); tvModel.setText(WheelData.getInstance().getModel()); diff --git a/app/src/main/java/com/cooper/wheellog/views/WheelView.java b/app/src/main/java/com/cooper/wheellog/views/WheelView.java index 6d9ffa84..c81ce4dd 100644 --- a/app/src/main/java/com/cooper/wheellog/views/WheelView.java +++ b/app/src/main/java/com/cooper/wheellog/views/WheelView.java @@ -503,7 +503,7 @@ protected void onDraw(Canvas canvas) { canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mTopSpeed), mrRect.centerX(), mrRect.centerY() + boxTextHeight, textPaint); canvas.drawText(String.format(Locale.US, "%.1f " + getResources().getString(R.string.kmh), mAverageSpeed), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); if (mDistance < 1) - canvas.drawText(String.format(Locale.US, "%.0f " + getResources().getString(R.string.km), mDistance * 1000), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.0f " + getResources().getString(R.string.metre), mDistance * 1000), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); else canvas.drawText(String.format(Locale.US, "%.2f " + getResources().getString(R.string.km), mDistance), blRect.centerX(), blRect.centerY() + boxTextHeight, textPaint); diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index f1ea3ebb..d2193a4a 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -121,4 +121,5 @@ Отключить первое предупреждение. Отключить второе предупреждение. Включить предупреждения + м \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25399f29..4e23584b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -232,5 +232,6 @@ Turn off level 1 alarm Turn off level 2 alarm Turn on alarm + m From d150a5bb3b22b3bf32152a91afd6957b18a4b697 Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Tue, 19 Mar 2019 11:37:48 +0300 Subject: [PATCH 13/34] finish inmotion localization --- app/src/main/res/values-ru-rRU/strings.xml | 10 ++++++++ app/src/main/res/values/strings.xml | 10 ++++++++ app/src/main/res/xml/preferences_inmotion.xml | 24 +++++++++---------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index d2193a4a..d4f452bd 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -122,4 +122,14 @@ Отключить второе предупреждение. Включить предупреждения м + Включение фонаря + Включает передний фонарь для освещения дороги. + Включает светодиодную подсветку + Включает бугущие огни светодиодов по бокам. + Отключить кнопку на ручке + Колесо больше не будет отключаться когда кнопка на ручке будет нажата. + Уровень звука + Регулировка уровня громкости встроенных колонок. + Уровень наклона педалей + Изменение угла тангажа (вниз/вверх) педалей. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e23584b..5c8474d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,5 +233,15 @@ Turn off level 2 alarm Turn on alarm m + Switch on the light + Activate headlight + Switch on wheel illumination + Activate leds on the sides + Disable handle button + Wheel will not disable when you press the button on the handle + Speaker volume + Change built-in speaker volume + Pedals + Horizontal pedals adjustment diff --git a/app/src/main/res/xml/preferences_inmotion.xml b/app/src/main/res/xml/preferences_inmotion.xml index 107f6ceb..ee30ac15 100644 --- a/app/src/main/res/xml/preferences_inmotion.xml +++ b/app/src/main/res/xml/preferences_inmotion.xml @@ -5,35 +5,35 @@ + android:title="@string/on_headlight_title" + android:summary="@string/on_headlight_description" /> + android:summary="@string/leds_settings_description" /> + android:title="@string/disable_handle_button_title" + android:summary="@string/disable_handle_button_description" /> Date: Tue, 19 Mar 2019 11:42:13 +0300 Subject: [PATCH 14/34] finish ninebot_z localization --- app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/preferences_ninebot_z.xml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index d4f452bd..19730fe0 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -132,4 +132,5 @@ Регулировка уровня громкости встроенных колонок. Уровень наклона педалей Изменение угла тангажа (вниз/вверх) педалей. + К сожалению настройки для вашего колеса пока не доступны. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c8474d7..11dae7eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -243,5 +243,6 @@ Change built-in speaker volume Pedals Horizontal pedals adjustment + Wheel settings for Ninebot Z not available yet diff --git a/app/src/main/res/xml/preferences_ninebot_z.xml b/app/src/main/res/xml/preferences_ninebot_z.xml index 33e4aec2..4d80ef14 100644 --- a/app/src/main/res/xml/preferences_ninebot_z.xml +++ b/app/src/main/res/xml/preferences_ninebot_z.xml @@ -6,6 +6,6 @@ + android:title="@string/ninebotz_settings_title"/> \ No newline at end of file From b1373ca660eaff99e0b056ba2e2ca2c5af7294ba Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Tue, 19 Mar 2019 12:51:07 +0300 Subject: [PATCH 15/34] finish localization --- app/src/main/java/com/cooper/wheellog/MainActivity.java | 6 +++--- .../main/java/com/cooper/wheellog/views/WheelView.java | 2 +- app/src/main/res/values-ru-rRU/strings.xml | 8 +++++--- app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index 2c5be937..304b0460 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -587,13 +587,13 @@ private void updateScreen(boolean updateGraph) { tvTotalDistance.setText(String.format(Locale.US, "%.3f " + getString(R.string.km), WheelData.getInstance().getTotalDistanceDouble())); } - tvVoltage.setText(String.format(Locale.US, "%.2fV", WheelData.getInstance().getVoltageDouble())); + tvVoltage.setText(String.format(Locale.US, "%.2f " + getString(R.string.volt), WheelData.getInstance().getVoltageDouble())); tvTemperature.setText(String.format(Locale.US, "%d°C", WheelData.getInstance().getTemperature())); tvTemperature2.setText(String.format(Locale.US, "%d°C", WheelData.getInstance().getTemperature2())); tvAngle.setText(String.format(Locale.US, "%.2f°", WheelData.getInstance().getAngle())); tvRoll.setText(String.format(Locale.US, "%.2f°", WheelData.getInstance().getRoll())); - tvCurrent.setText(String.format(Locale.US, "%.2fA", WheelData.getInstance().getCurrentDouble())); - tvPower.setText(String.format(Locale.US, "%.2fW", WheelData.getInstance().getPowerDouble())); + tvCurrent.setText(String.format(Locale.US, "%.2f " + getString(R.string.amp), WheelData.getInstance().getCurrentDouble())); + tvPower.setText(String.format(Locale.US, "%.2f " + getString(R.string.watt), WheelData.getInstance().getPowerDouble())); tvBattery.setText(String.format(Locale.US, "%d%%", WheelData.getInstance().getBatteryLevel())); tvFanStatus.setText(WheelData.getInstance().getFanStatus() == 0 ? getString(R.string.off) : getString(R.string.on)); tvVersion.setText(String.format(Locale.US, "%s", WheelData.getInstance().getVersion())); diff --git a/app/src/main/java/com/cooper/wheellog/views/WheelView.java b/app/src/main/java/com/cooper/wheellog/views/WheelView.java index c81ce4dd..472ca918 100644 --- a/app/src/main/java/com/cooper/wheellog/views/WheelView.java +++ b/app/src/main/java/com/cooper/wheellog/views/WheelView.java @@ -490,7 +490,7 @@ protected void onDraw(Canvas canvas) { canvas.drawText(getResources().getString(R.string.distance), blRect.centerX(), blRect.centerY() - (box_inner_padding / 2), textPaint); canvas.drawText(getResources().getString(R.string.total), brRect.centerX(), brRect.centerY() - (box_inner_padding / 2), textPaint); - canvas.drawText(String.format(Locale.US, "%.2fV", mVoltage), tlRect.centerX(), tlRect.centerY() + boxTextHeight, textPaint); + canvas.drawText(String.format(Locale.US, "%.2f " + getResources().getString(R.string.volt), mVoltage), tlRect.centerX(), tlRect.centerY() + boxTextHeight, textPaint); //canvas.drawText(String.format(Locale.US, "%.2fW", mCurrent), trRect.centerX(), trRect.centerY() + boxTextHeight, textPaint); canvas.drawText(mCurrentTime, mlRect.centerX(), mlRect.centerY() + boxTextHeight + (box_inner_padding / 2), textPaint); diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 19730fe0..2d3c81f4 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -11,7 +11,7 @@ Подключение Макс. скорость Батарея - Дистанция + Расстояние Поездка Мой пробег Время работы @@ -20,11 +20,11 @@ Ток Мощность Температура - Пройдено + Пробег Сред. скорость Сред. движения Темп. процессора - Полный пробег + Пробег Название Модель Версия @@ -133,4 +133,6 @@ Уровень наклона педалей Изменение угла тангажа (вниз/вверх) педалей. К сожалению настройки для вашего колеса пока не доступны. + В + Вт \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 11dae7eb..a1ed16ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,5 +244,7 @@ Pedals Horizontal pedals adjustment Wheel settings for Ninebot Z not available yet + V + W From aab0b58640b2359aaccfcf454136b2dbfd6671ee Mon Sep 17 00:00:00 2001 From: DotarSoja <5547171@mail.ru> Date: Tue, 26 Mar 2019 09:31:24 +0300 Subject: [PATCH 16/34] fix grammar and typos --- app/src/main/res/values-ru-rRU/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 2d3c81f4..c22d8893 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -40,7 +40,7 @@ км/ч миль/ч км - милли + мили Настройка Сигнала Скорость подъема педалей 4 звуковых сигнала @@ -78,8 +78,8 @@ А Максимальная скорость Ограничение спидометра на главном экране. - Использовать милли - Показывать милли вместо киллометров. + Использовать мили + Показывать мили вместо километров. Включить предупреждения Телефон будет вибрировать во время предупреждения. Отключить вибросигнал @@ -108,7 +108,7 @@ Местоположение Добавить текущую позицию в лог. Использование GPS - Использовать данные GPS для точного определения местоположения. При использовании GPS повышается энергопотребление телефона, если параметр отключен используются даенные сотовой связи. + Использовать данные GPS для точного определения местоположения. При использовании GPS повышается энергопотребление телефона, а если параметр отключен, используются данные сотовой связи. Выкл./Вкл./Строб Мягкие/Средние/Жесткие Режим предупреждения From f0eb4a0d1c5d4becb8cf55ea7962b30be484fc32 Mon Sep 17 00:00:00 2001 From: palachzzz <7zzzzzzzx@gmail.com> Date: Fri, 3 May 2019 13:09:57 +0300 Subject: [PATCH 17/34] update gradle --- app/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 50cc0c72..63840932 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ repositories { android { compileSdkVersion 26 - buildToolsVersion '27.0.3' + buildToolsVersion '28.0.3' lintOptions { abortOnError false diff --git a/build.gradle b/build.gradle index 8878386c..9e97ec76 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.4.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // NOTE: Do not place your application dependencies here; they belong From aff558b5c6052d5d40f02ec061a7774ff2994819 Mon Sep 17 00:00:00 2001 From: palachzzz <7zzzzzzzx@gmail.com> Date: Fri, 3 May 2019 22:58:12 +0300 Subject: [PATCH 18/34] gw fix lang fix --- app/build.gradle | 4 +- app/release/output.json | 2 +- .../com/cooper/wheellog/MainActivity.java | 4 +- .../cooper/wheellog/PreferencesFragment.java | 6 +- app/src/main/res/values-ru-rRU/strings.xml | 7 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences_gotway.xml | 8 +- app/src/main/res/xml/preferences_inmotion.xml | 4 +- gradlew | 172 ------------------ 9 files changed, 25 insertions(+), 185 deletions(-) delete mode 100755 gradlew diff --git a/app/build.gradle b/app/build.gradle index 1dc08804..4bd0eb53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.cooper.wheellog" minSdkVersion 18 targetSdkVersion 26 - versionCode 37 - versionName "2.0.19_KS_RE" + versionCode 40 + versionName "2.0.21" buildConfigField 'String', 'BUILD_TIME', 'new java.text.SimpleDateFormat("HH:mm dd.MM.yyyy", java.util.Locale.US).format(new java.util.Date(' + System.currentTimeMillis() +'L))' } buildTypes { diff --git a/app/release/output.json b/app/release/output.json index 3d2f6b38..e4bd127d 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"2.0.19_KS_RE","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":40,"versionName":"2.0.21","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index af5379ca..48fa9513 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -968,7 +968,7 @@ private void loadPreferences() { boolean use_ratio = sharedPreferences.getBoolean(getString(R.string.use_ratio), false); WheelData.getInstance().setUseRatio(use_ratio); - int gotway_voltage = Integer.parseInt(sharedPreferences.getString(getString(R.string.gotway_voltage), "0")); + int gotway_voltage = Integer.parseInt(sharedPreferences.getString(getString(R.string.gotway_voltage), "1")); WheelData.getInstance().setGotwayVoltage(gotway_voltage); //boolean gotway_84v = sharedPreferences.getBoolean(getString(R.string.gotway_84v), false); @@ -1204,4 +1204,6 @@ private Fragment getPreferencesFragment() { return frag; } + + } \ No newline at end of file diff --git a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java index 2e632496..2ae2305d 100644 --- a/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java +++ b/app/src/main/java/com/cooper/wheellog/PreferencesFragment.java @@ -68,8 +68,8 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin if (SettingsUtil.isAutoUploadEnabled(getActivity()) && !mDataWarningDisplayed) { SettingsUtil.setAutoUploadEnabled(getActivity(), false); new AlertDialog.Builder(getActivity()) - .setTitle("Enable Auto Upload?") - .setMessage("Automatic uploading while not connected to WiFi will use your mobile data. This may result in charges from your network provider if you do not have a data plan.") + .setTitle(getString(R.string.enable_auto_upload_title)) // ("Enable Auto Upload?") + .setMessage(getString(R.string.enable_auto_upload_descriprion)) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mDataWarningDisplayed = true; @@ -340,7 +340,7 @@ public boolean onPreferenceClick(Preference preference) { String buildTime = BuildConfig.BUILD_TIME; new AlertDialog.Builder(getActivity()) .setTitle(R.string.about_app_title) - .setMessage(Html.fromHtml(String.format("Version %s
build at %s
by Palachzzz
palachzzz.wl@gmail.com", versionName, buildTime))) + .setMessage(Html.fromHtml(String.format("Version %s
build at %s
by Palachzzz
palachzzz.wl@gmail.com
Thanks to:
JumpMaster - project initiator
cedbossneo - Inmotion support
juliomap - Tizen support
MacPara - some improvements
datarsoja - KS alerts
and others!", versionName, buildTime))) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index c22d8893..d3df29bf 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -23,6 +23,8 @@ Пробег Сред. скорость Сред. движения + Наклон вперед + Наклон в бок Темп. процессора Пробег Название @@ -135,4 +137,9 @@ К сожалению настройки для вашего колеса пока не доступны. В Вт + Включить автозагрузку? + Автоматическая загрузка будет потреблять ваши мобильные данные, за это может взиматься дополнительная плата вашим мобильным оператором + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1ed16ea..d20cc265 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,6 +68,9 @@ Connection Icon Connection lost at %1s + Enable Auto Upload? + Automatic uploading while not connected to WiFi will use your mobile data. This may result in charges from your network provider if you do not have a data plan. + No permission to write to external storage. Logging cancelled. External storage is unavailable. Logging cancelled. Log location was requested but no permission to location data was provided. No location data will be logged. diff --git a/app/src/main/res/xml/preferences_gotway.xml b/app/src/main/res/xml/preferences_gotway.xml index 4b256106..3c7cb785 100644 --- a/app/src/main/res/xml/preferences_gotway.xml +++ b/app/src/main/res/xml/preferences_gotway.xml @@ -35,11 +35,11 @@ android:key="@string/wheel_max_speed" android:title="@string/max_speed_title" android:summary="@string/tilt_back_description" - android:defaultValue="25" + android:defaultValue="30" android:enabled="true" sample:msbp_minValue="0" - sample:msbp_maxValue="50" - sample:msbp_interval="1" + sample:msbp_maxValue="48" + sample:msbp_interval="3" sample:msbp_measurementUnit="@string/kmh" sample:msbp_dialogEnabled="true"/> @@ -55,7 +55,7 @@ android:summary="@string/is_gotway_mcm_description" /> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" From cd3fb47eae025767f3d0ce48212c4d7a5328546d Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Tue, 21 May 2019 12:13:31 +0800 Subject: [PATCH 19/34] Fix usage of carriage returns in csv exports to google drive --- app/src/main/java/com/cooper/wheellog/GoogleDriveService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/GoogleDriveService.java b/app/src/main/java/com/cooper/wheellog/GoogleDriveService.java index d2682a83..0e933c49 100644 --- a/app/src/main/java/com/cooper/wheellog/GoogleDriveService.java +++ b/app/src/main/java/com/cooper/wheellog/GoogleDriveService.java @@ -197,7 +197,7 @@ public void onResult(@NonNull DriveApi.DriveContentsResult result) { String line; while ((line = br.readLine()) != null) - writer.append(line).append('\r'); + writer.append(line).append('\n'); br.close(); writer.close(); From ce099a738c7ebb9793ff1feb574c92e85db9bed4 Mon Sep 17 00:00:00 2001 From: Chow Loong Jin Date: Tue, 21 May 2019 12:15:45 +0800 Subject: [PATCH 20/34] Fix rockwheel detection mBtName isn't populated (is an empty string) if we don't start from a fresh bluetooth pairing with the GT16, so the battery gauge is wrongly calculated based on a 67.2V battery instead. Instead, check mName for a "ROCKW" prefix, as this is available even on subsequent sessions. --- app/src/main/java/com/cooper/wheellog/WheelData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 4d54d52f..8bd7980c 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -855,7 +855,7 @@ private boolean decodeKingSong(byte[] data) { int battery; - if ((mModel.compareTo("KS-18L") == 0) || (mBtName.compareTo("RW") == 0 )) { + if ((mModel.compareTo("KS-18L") == 0) || (mBtName.compareTo("RW") == 0) || (mName.startsWith("ROCKW"))) { if (mVoltage > 8350) { battery = 100; @@ -1206,7 +1206,7 @@ boolean detectWheel(BluetoothLeService bluetoothService) { mContext.sendBroadcast(intent); Timber.i("Protocol recognized as %s", wheel_Type); //System.out.println("WheelRecognizedWD"); - if (mContext.getResources().getString(R.string.gotway).equals(wheel_Type) && (mBtName.equals("RW"))) { + if (mContext.getResources().getString(R.string.gotway).equals(wheel_Type) && (mBtName.equals("RW") || mName.startsWith("ROCKW"))) { Timber.i("It seems to be RochWheel, force to Kingsong proto"); wheel_Type = mContext.getResources().getString(R.string.kingsong); } From 7297eb318fcd87a6800df5646dd8eea5daab9eac Mon Sep 17 00:00:00 2001 From: palachzzz <7zzzzzzzx@gmail.com> Date: Thu, 6 Jun 2019 23:56:50 +0300 Subject: [PATCH 21/34] beeps + alarms --- app/build.gradle | 6 +++--- app/release/output.json | 2 +- app/src/main/java/com/cooper/wheellog/WheelData.java | 4 ++++ app/src/main/res/xml/preferences_alarms.xml | 8 ++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4bd0eb53..46d8f4a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,9 +16,9 @@ android { defaultConfig { applicationId "com.cooper.wheellog" minSdkVersion 18 - targetSdkVersion 26 - versionCode 40 - versionName "2.0.21" + targetSdkVersion 25 + versionCode 41 + versionName "2.0.22" buildConfigField 'String', 'BUILD_TIME', 'new java.text.SimpleDateFormat("HH:mm dd.MM.yyyy", java.util.Locale.US).format(new java.util.Date(' + System.currentTimeMillis() +'L))' } buildTypes { diff --git a/app/release/output.json b/app/release/output.json index e4bd127d..e196f7e0 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":40,"versionName":"2.0.21","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":41,"versionName":"2.0.22","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 4d54d52f..d084292f 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -8,6 +8,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Vibrator; +import android.media.ToneGenerator; +import android.media.AudioManager; import android.text.InputType; import android.widget.EditText; @@ -769,6 +771,8 @@ private void raiseAlarm(ALARM_TYPE alarmType, Context mContext) { mContext.sendBroadcast(intent); if (v.hasVibrator() && !mDisablePhoneVibrate) v.vibrate(pattern, -1); + ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100); + toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 200); } void decodeResponse(byte[] data, Context mContext) { diff --git a/app/src/main/res/xml/preferences_alarms.xml b/app/src/main/res/xml/preferences_alarms.xml index 48e2c921..db9e202f 100644 --- a/app/src/main/res/xml/preferences_alarms.xml +++ b/app/src/main/res/xml/preferences_alarms.xml @@ -24,7 +24,7 @@ android:defaultValue="29" sample:dependency="alarms_enabled" sample:msbp_minValue="0" - sample:msbp_maxValue="50" + sample:msbp_maxValue="100" sample:msbp_interval="1" sample:msbp_measurementUnit="@string/kmh" sample:msbp_dialogEnabled="true"/> @@ -53,7 +53,7 @@ android:defaultValue="0" sample:dependency="alarms_enabled" sample:msbp_minValue="0" - sample:msbp_maxValue="50" + sample:msbp_maxValue="100" sample:msbp_interval="1" sample:msbp_measurementUnit="@string/kmh" sample:msbp_dialogEnabled="true"/> @@ -82,7 +82,7 @@ android:defaultValue="0" sample:dependency="alarms_enabled" sample:msbp_minValue="0" - sample:msbp_maxValue="50" + sample:msbp_maxValue="100" sample:msbp_interval="1" sample:msbp_measurementUnit="@string/kmh" sample:msbp_dialogEnabled="true"/> @@ -111,7 +111,7 @@ android:defaultValue="35" sample:dependency="alarms_enabled" sample:msbp_minValue="0" - sample:msbp_maxValue="150" + sample:msbp_maxValue="200" sample:msbp_interval="1" sample:msbp_measurementUnit="@string/amp" sample:msbp_dialogEnabled="true"/> From d51e05f662c1c20012760a94ee2feb6bc1394ea1 Mon Sep 17 00:00:00 2001 From: palachzzz <7zzzzzzzx@gmail.com> Date: Wed, 12 Jun 2019 23:25:57 +0300 Subject: [PATCH 22/34] fix --- app/build.gradle | 6 +++--- app/release/output.json | 2 +- app/src/main/java/com/cooper/wheellog/WheelData.java | 8 ++++---- app/src/main/res/xml/preferences_gotway.xml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 46d8f4a1..f54ff573 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,9 +16,9 @@ android { defaultConfig { applicationId "com.cooper.wheellog" minSdkVersion 18 - targetSdkVersion 25 - versionCode 41 - versionName "2.0.22" + targetSdkVersion 26 + versionCode 43 + versionName "2.0.23" buildConfigField 'String', 'BUILD_TIME', 'new java.text.SimpleDateFormat("HH:mm dd.MM.yyyy", java.util.Locale.US).format(new java.util.Date(' + System.currentTimeMillis() +'L))' } buildTypes { diff --git a/app/release/output.json b/app/release/output.json index e196f7e0..07e9fe73 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":41,"versionName":"2.0.22","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":43,"versionName":"2.0.23","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/main/java/com/cooper/wheellog/WheelData.java b/app/src/main/java/com/cooper/wheellog/WheelData.java index 36481aa3..e5343223 100644 --- a/app/src/main/java/com/cooper/wheellog/WheelData.java +++ b/app/src/main/java/com/cooper/wheellog/WheelData.java @@ -756,15 +756,15 @@ private void raiseAlarm(ALARM_TYPE alarmType, Context mContext) { switch (alarmType) { case SPEED: - pattern = new long[]{0, 300, 150, 300, 150, 500}; + pattern = new long[]{0, 100, 100}; mSpeedAlarmExecuted = true; break; case CURRENT: - pattern = new long[]{0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100}; + pattern = new long[]{0, 50, 50, 50, 50}; mCurrentAlarmExecuted = true; break; case TEMPERATURE: - pattern = new long[]{0, 500, 100, 100, 100, 500, 100, 100, 100, 500, 100, 100, 100}; + pattern = new long[]{0, 500, 500}; mCurrentAlarmExecuted = true; break; } @@ -859,7 +859,7 @@ private boolean decodeKingSong(byte[] data) { int battery; - if ((mModel.compareTo("KS-18L") == 0) || (mBtName.compareTo("RW") == 0) || (mName.startsWith("ROCKW"))) { + if ((mModel.compareTo("KS-18L") == 0) || (mModel.compareTo("KS-16X") == 0) ||(mBtName.compareTo("RW") == 0) || (mName.startsWith("ROCKW"))) { if (mVoltage > 8350) { battery = 100; diff --git a/app/src/main/res/xml/preferences_gotway.xml b/app/src/main/res/xml/preferences_gotway.xml index 3c7cb785..1beffb3b 100644 --- a/app/src/main/res/xml/preferences_gotway.xml +++ b/app/src/main/res/xml/preferences_gotway.xml @@ -58,7 +58,7 @@ android:defaultValue="1" android:enabled="true" android:entries="@array/gotway_voltage" - android:entryValues="@array/gotway_voltage" + android:entryValues="@array/gotway_voltage_values" android:key="@string/gotway_voltage" android:summary="@string/battary_voltage_description" android:title="@string/battery_voltage_title" /> From 3a491f12051c33236a7ab7094847bdcff9ac623f Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Wed, 31 Jul 2019 21:22:43 -0400 Subject: [PATCH 23/34] Bringing in the garmin connectiq lib --- app/build.gradle | 5 +++-- app/libs/connectiq.jar | Bin 0 -> 76935 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 app/libs/connectiq.jar diff --git a/app/build.gradle b/app/build.gradle index f54ff573..6e5d3260 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,7 +33,7 @@ android { dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:design:25.1.0' implementation 'com.android.support:gridlayout-v7:25.1.0' implementation 'com.google.android.gms:play-services-drive:10.0.1' @@ -42,6 +42,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.3.1' implementation 'com.pavelsikun:material-seekbar-preference:2.3.0+' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.0' - implementation "com.github.hotchemi:permissionsdispatcher:2.2.0" + implementation 'com.github.hotchemi:permissionsdispatcher:2.2.0' annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:2.2.0" + implementation files('libs/connectiq.jar') } diff --git a/app/libs/connectiq.jar b/app/libs/connectiq.jar new file mode 100644 index 0000000000000000000000000000000000000000..914b6392a607a590338e04631a0f50fd9507104e GIT binary patch literal 76935 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 Date: Wed, 31 Jul 2019 21:25:57 -0400 Subject: [PATCH 24/34] Initial commit of the GarminConnectIQ service --- app/src/main/AndroidManifest.xml | 4 +- .../com/cooper/wheellog/GarminConnectIQ.java | 412 ++++++++++++++++++ .../cooper/wheellog/utils/SettingsUtil.java | 4 + app/src/main/res/values/strings.xml | 8 +- app/src/main/res/xml/preferences_watch.xml | 6 +- 5 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 83d20957..7fe5c2ba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,7 +58,9 @@ - + + + mDevices; + private IQDevice mDevice; + private IQApp mMyApp; + + public static boolean isInstanceCreated() { + return instance != null; + } + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "onBind"); + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG,"onStartCommand"); + super.onStartCommand(intent, flags, startId); + + instance = this; + + // Setup Connect IQ + mMyApp = new IQApp(APP_ID); + mConnectIQ = ConnectIQ.getInstance(this, IQConnectType.WIRELESS); + mConnectIQ.initialize(this, true, this); + + return START_STICKY; + } + + @Override + public void onDestroy() { + Log.d(TAG,"onDestroy"); + super.onDestroy(); + + cancelRefreshTimer(); + + try { + mConnectIQ.unregisterAllForEvents(); + mConnectIQ.shutdown(this); + } catch (InvalidStateException e) { + // This is usually because the SDK was already shut down + // so no worries. + } + + instance = null; + } + + // General METHODS + private void populateDeviceList() { + Log.d(TAG,"populateDeviceList"); + + try { + mDevices = mConnectIQ.getKnownDevices(); + + if (mDevices != null && !mDevices.isEmpty()) { + mDevice = mDevices.get(0); + registerWithDevice(); + } + + } catch (InvalidStateException e) { + // This generally means you forgot to call initialize(), but since + // we are in the callback for initialize(), this should never happen + } catch (ServiceUnavailableException e) { + // This will happen if for some reason your app was not able to connect + // to the ConnectIQ service running within Garmin Connect Mobile. This + // could be because Garmin Connect Mobile is not installed or needs to + // be upgraded. + Toast.makeText(this, R.string.garmin_connectiq_service_unavailable_message, Toast.LENGTH_LONG).show(); + } + } + + private void registerWithDevice() { + Log.d(TAG,"registerWithDevice"); + + if (mDevice != null && mSdkReady) { + // Register for device status updates + try { + mConnectIQ.registerForDeviceEvents(mDevice, this); + } catch (InvalidStateException e) { + Log.wtf(TAG, "InvalidStateException: We should not be here!"); + } + + // Register for application status updates + try { + mConnectIQ.getApplicationInfo(APP_ID, mDevice, this); + } catch (InvalidStateException e1) { + Log.d(TAG, "e1: " + e1.getMessage()); + } catch (ServiceUnavailableException e1) { + Log.d(TAG, "e2: " + e1.getMessage()); + } + + // Register to receive messages from the device + try { + mConnectIQ.registerForAppEvents(mDevice, mMyApp, this); + } catch (InvalidStateException e) { + Toast.makeText(this, "ConnectIQ is not in a valid state", Toast.LENGTH_LONG).show(); + } + } + } + + private void unregisterWithDevice() { + Log.d(TAG,"unregisterWithDevice"); + + if (mDevice != null && mSdkReady) { + // It is a good idea to unregister everything and shut things down to + // release resources and prevent unwanted callbacks. + try { + mConnectIQ.unregisterForDeviceEvents(mDevice); + + if (mMyApp != null) { + mConnectIQ.unregisterForApplicationEvents(mDevice, mMyApp); + } + } catch (InvalidStateException e) { + } + } + } + + private void cancelRefreshTimer() { + if (keepAliveTimer != null) { + keepAliveTimer.cancel(); + keepAliveTimer = null; + } + } + + private void startRefreshTimer() { + TimerTask timerTask = new TimerTask() { + @Override + public void run() { + handler.post(new Runnable() { + public void run() { + refreshData(); + } + }); + } + }; + + keepAliveTimer = new Timer(); + keepAliveTimer.scheduleAtFixedRate(timerTask, 0, 1500); // 1.5cs + } + + private void refreshData() { + if (WheelData.getInstance() == null) + return; + + try { + HashMap data = new HashMap(); + + lastSpeed = WheelData.getInstance().getSpeed() / 10; + data.put(MESSAGE_KEY_SPEED, lastSpeed); + + lastBattery = WheelData.getInstance().getBatteryLevel(); + data.put(MESSAGE_KEY_BATTERY, lastBattery); + + lastTemperature = WheelData.getInstance().getTemperature(); + data.put(MESSAGE_KEY_TEMPERATURE, lastTemperature); + + lastFanStatus = WheelData.getInstance().getFanStatus(); + data.put(MESSAGE_KEY_FAN_STATE, lastFanStatus); + + lastConnectionState = WheelData.getInstance().isConnected(); + data.put(MESSAGE_KEY_BT_STATE, lastConnectionState); + + // TODO: hey I should actually make the watch vibrate at a certain speed! + data.put(MESSAGE_KEY_VIBE_ALERT, false); + data.put(MESSAGE_KEY_USE_MPH, SettingsUtil.isUseMPH(GarminConnectIQ.this)); + data.put(MESSAGE_KEY_MAX_SPEED, SettingsUtil.getMaxSpeed(GarminConnectIQ.this)); + + lastRideTime = WheelData.getInstance().getRideTime(); + data.put(MESSAGE_KEY_RIDE_TIME, lastRideTime); + + lastDistance = WheelData.getInstance().getDistance(); + data.put(MESSAGE_KEY_DISTANCE, lastDistance/100); + + lastTopSpeed = WheelData.getInstance().getTopSpeed(); + data.put(MESSAGE_KEY_TOP_SPEED, lastTopSpeed/10); + + lastPower = (int)WheelData.getInstance().getPowerDouble(); + data.put(MESSAGE_KEY_POWER, lastPower); + + HashMap message = new HashMap(); + message.put(MESSAGE_KEY_MSG_TYPE, MessageType.EUC_DATA.ordinal()); + message.put(MESSAGE_KEY_MSG_DATA, new MonkeyHash(data)); + + try { + mConnectIQ.sendMessage(mDevice, mMyApp, message, new IQSendMessageListener() { + + @Override + public void onMessageStatus(IQDevice device, IQApp app, IQMessageStatus status) { + Log.d(TAG, "message status: " + status.name()); + + if (status.name() != "SUCCESS") + Toast.makeText(GarminConnectIQ.this, status.name(), Toast.LENGTH_LONG).show(); + } + + }); + } catch (InvalidStateException e) { + Log.e(TAG, "ConnectIQ is not in a valid state"); + Toast.makeText(this, "ConnectIQ is not in a valid state", Toast.LENGTH_LONG).show(); + } catch (ServiceUnavailableException e) { + Log.e(TAG, "ConnectIQ service is unavailable. Is Garmin Connect Mobile installed and running?"); + Toast.makeText(this, "ConnectIQ service is unavailable. Is Garmin Connect Mobile installed and running?", Toast.LENGTH_LONG).show(); + } + } catch (Exception ex) { + Log.e(TAG, "refreshData", ex); + Toast.makeText(this, "Error refreshing data", Toast.LENGTH_SHORT).show(); + } + } + + // IQApplicationInfoListener METHODS + @Override + public void onApplicationInfoReceived(IQApp app) { + Log.d(TAG,"onApplicationInfoReceived"); + Log.d(TAG, app.toString()); + } + + @Override + public void onApplicationNotInstalled(String arg0) { + Log.d(TAG,"onApplicationNotInstalled"); + + // The WheelLog app is not installed on the device so we have + // to let the user know to install it. + cancelRefreshTimer(); // no point in sending data... + + Toast.makeText(this, R.string.garmin_connectiq_missing_app_message, Toast.LENGTH_LONG).show(); + try { + mConnectIQ.openStore(APP_ID); + } catch (InvalidStateException e) { + + } catch (ServiceUnavailableException e) { + + } + } + + // IQDeviceEventListener METHODS + @Override + public void onDeviceStatusChanged(IQDevice device, IQDeviceStatus status) { + Log.d(TAG,"onDeviceStatusChanged"); + Log.d(TAG, "status is:" + status.name()); + + switch(status.name()) { + case "CONNECTED": + startRefreshTimer(); + break; + case "NOT_PAIRED": + case "NOT_CONNECTED": + case "UNKNOWN": + cancelRefreshTimer(); + } + + // TODO: make sure the device passed matches the one that's selected + // mStatusText.setText(status.name()); + } + + + // IQApplicationEventListener + @Override + public void onMessageReceived(IQDevice device, IQApp app, List message, IQMessageStatus status) { + Log.d(TAG,"onMessageReceived"); + + // We know from our widget that it will only ever send us strings, but in case + // we get something else, we are simply going to do a toString() on each object in the + // message list. + StringBuilder builder = new StringBuilder(); + + if (message.size() > 0) { + for (Object o : message) { + if (o == null) { + builder.append(" received"); + } else if (o instanceof HashMap) { + try { + HashMap msg = (HashMap)o; + int msgType = (int)msg.get(MESSAGE_KEY_MSG_TYPE); + if (msgType == MessageType.PLAY_HORN.ordinal()) { + playHorn(); + } + builder = null; + } catch (Exception ex) { + builder.append("MonkeyHash received:\n\n"); + builder.append(o.toString()); + } + } else { + builder.append(o.toString()); + builder.append("\r\n"); + } + } + } else { + builder.append("Received an empty message from the ConnectIQ application"); + } + + if (builder != null) { + Toast.makeText(getApplicationContext(), builder.toString(), Toast.LENGTH_SHORT).show(); + } + } + + // ConnectIQListener METHODS + @Override + public void onInitializeError(IQSdkErrorStatus errStatus) { + Log.d(TAG,"sdk initialization error"); + mSdkReady = false; + } + + @Override + public void onSdkReady() { + Log.d(TAG,"sdk is ready"); + mSdkReady = true; + populateDeviceList(); + } + + @Override + public void onSdkShutDown() { + Log.d(TAG,"sdk shut down"); + mSdkReady = false; + } + + public void playHorn() { + Context context = getApplicationContext(); + + int horn_mode = SettingsUtil.getHornMode(context); + if (horn_mode == 1) { + final Intent hornIntent = new Intent(ACTION_REQUEST_KINGSONG_HORN); + context.sendBroadcast(hornIntent); + } else if (horn_mode == 2) { + MediaPlayer mp = MediaPlayer.create(context, R.raw.bicycle_bell); + mp.start(); + mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + mp.release(); + } + }); + } + } +} diff --git a/app/src/main/java/com/cooper/wheellog/utils/SettingsUtil.java b/app/src/main/java/com/cooper/wheellog/utils/SettingsUtil.java index 310e5b85..fe43d928 100644 --- a/app/src/main/java/com/cooper/wheellog/utils/SettingsUtil.java +++ b/app/src/main/java/com/cooper/wheellog/utils/SettingsUtil.java @@ -98,6 +98,10 @@ public static int getHornMode(Context context) { return Integer.parseInt(getSharedPreferences(context).getString(context.getString(R.string.horn_mode), "0")); } + public static boolean getGarminConnectIQEnable(Context context) { + return getSharedPreferences(context).getBoolean(context.getString(R.string.garmin_connectiq_enable), false); + } + //Inmotion Specific, but can be the same for other wheels public static boolean hasPasswordForWheel(Context context, String id) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d20cc265..8d2ce0a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,8 +140,14 @@ // WATCH PREFERENCES horn_mode + garmin_connectiq_enable + Garmin ConnectIQ + Enable connectivity to Garmin ConnectIQ + + // Garmin ConnectIQ dialogs + The WheelLog App used with this application is not installed on your ConnectIQ device. Please install the widget and try again. + The ConnectIQ service is unavailable. - // TRIP PREFERENCES reset_top_speed reset_user_distance diff --git a/app/src/main/res/xml/preferences_watch.xml b/app/src/main/res/xml/preferences_watch.xml index 0749c5a7..de913d63 100644 --- a/app/src/main/res/xml/preferences_watch.xml +++ b/app/src/main/res/xml/preferences_watch.xml @@ -9,5 +9,9 @@ android:key="@string/horn_mode" android:summary="@string/horn_mode_description" android:title="@string/horn_mode_title" /> - + \ No newline at end of file From 026d730e0bd013f6bb5249854b58668df879e18a Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Wed, 31 Jul 2019 21:26:47 -0400 Subject: [PATCH 25/34] Start the GarminConnectIQ service at the same time as pebble --- .../java/com/cooper/wheellog/MainActivity.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/com/cooper/wheellog/MainActivity.java b/app/src/main/java/com/cooper/wheellog/MainActivity.java index 48fa9513..1cf7acee 100644 --- a/app/src/main/java/com/cooper/wheellog/MainActivity.java +++ b/app/src/main/java/com/cooper/wheellog/MainActivity.java @@ -854,6 +854,7 @@ public void onPause() { @Override protected void onDestroy() { stopPebbleService(); + stopGarminConnectIQ(); stopLoggingService(); WheelData.getInstance().full_reset(); if (mBluetoothLeService != null) { @@ -904,6 +905,10 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.miWatch: togglePebbleService(); + if (SettingsUtil.getGarminConnectIQEnable(this)) + toggleGarminConnectIQ(); + else + stopGarminConnectIQ(); return true; default: return super.onOptionsItemSelected(item); @@ -1081,6 +1086,18 @@ private void togglePebbleService() { startService(pebbleServiceIntent); } + private void stopGarminConnectIQ() { + if (GarminConnectIQ.isInstanceCreated()) + toggleGarminConnectIQ(); + } + private void toggleGarminConnectIQ() { + Intent garminConnectIQIntent = new Intent(getApplicationContext(), GarminConnectIQ.class); + if (GarminConnectIQ.isInstanceCreated()) + stopService(garminConnectIQIntent); + else + startService(garminConnectIQIntent); + } + private void startBluetoothService() { Intent bluetoothServiceIntent = new Intent(getApplicationContext(), BluetoothLeService.class); startService(bluetoothServiceIntent); From b99dae64fc2f989bf3b54a68a315cb3187801112 Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Wed, 31 Jul 2019 21:34:37 -0400 Subject: [PATCH 26/34] Link to the watch app and source --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0d8889ba..edbe787c 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,8 @@ https://github.com/JumpMaster/WheelLogPebble Samsung Gear app code https://github.com/juliomap/WheelLog-Tizen + +Garmin Connect IQ app code + +https://githubb.com/marccardinal/WheelLog-Garmin-ConnectIQ +Also available on the ConnectIQ store https://apps.garmin.com/en-US/apps/07a231a9-3f2f-4762-b0bb-b8a0b5594f40 From f99b82e32cecb51cec19801cb82b405a5682f89b Mon Sep 17 00:00:00 2001 From: "Marc Vieira-Cardinal (VA2MVC)" Date: Wed, 31 Jul 2019 23:00:49 -0400 Subject: [PATCH 27/34] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index edbe787c..3c6487f4 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,5 @@ https://github.com/juliomap/WheelLog-Tizen Garmin Connect IQ app code https://githubb.com/marccardinal/WheelLog-Garmin-ConnectIQ -Also available on the ConnectIQ store https://apps.garmin.com/en-US/apps/07a231a9-3f2f-4762-b0bb-b8a0b5594f40 + +The watch application is also available for download directly on the ConnectIQ store https://apps.garmin.com/en-US/apps/07a231a9-3f2f-4762-b0bb-b8a0b5594f40 From 14aad165228bf067c08dc41f71f2d488aa3febcc Mon Sep 17 00:00:00 2001 From: "Marc Vieira-Cardinal (VA2MVC)" Date: Wed, 31 Jul 2019 23:01:29 -0400 Subject: [PATCH 28/34] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c6487f4..b7031c12 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@ One more try to make it working with Inmotion V8 Added support for Samsung Gear Tizen based watches -Pebble app code +## Pebble app code https://github.com/JumpMaster/WheelLogPebble -Samsung Gear app code +## Samsung Gear app code https://github.com/juliomap/WheelLog-Tizen -Garmin Connect IQ app code +## Garmin Connect IQ app code https://githubb.com/marccardinal/WheelLog-Garmin-ConnectIQ From 486fb546b7e0f4e57fa61cbcba6168a29ea39136 Mon Sep 17 00:00:00 2001 From: nicolas2k Date: Mon, 5 Aug 2019 19:12:35 +0200 Subject: [PATCH 29/34] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7031c12..e261759e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,6 @@ https://github.com/juliomap/WheelLog-Tizen ## Garmin Connect IQ app code -https://githubb.com/marccardinal/WheelLog-Garmin-ConnectIQ +https://github.com/marccardinal/WheelLog-Garmin-ConnectIQ The watch application is also available for download directly on the ConnectIQ store https://apps.garmin.com/en-US/apps/07a231a9-3f2f-4762-b0bb-b8a0b5594f40 From 4ca956be827480e9a999eac80e0a8513c5b2bd79 Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Wed, 7 Aug 2019 23:18:50 -0400 Subject: [PATCH 30/34] Temporarily disabled the android to watch push of data and introduced nanohttpd Details about the issue can be found here: https://forums.garmin.com/developer/connect-iq/f/legacy-bug-reports/5144/failure_during_transfer-issue-again-now-using-comm-sample People have reported receiving a FAILURE_DURING_TRANSFER when sending messages to the watch with a short interval, this also seems to happen after a certain number of messages have been sent to the watch according to people on the forum. On my end two people one with a F5X+ and another with a VivoActive 3 Music have reporting the issue intermittently too. So instead of having the android app push the data I am now having the watch poll the data via a /data request to localhost on an ephemeral port. This is a workaround until Garmin decides to tackle the underlying issue (the forum post is from 2yrs+ ago) --- app/build.gradle | 1 + .../com/cooper/wheellog/GarminConnectIQ.java | 164 +++++++++++++++--- 2 files changed, 144 insertions(+), 21 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6e5d3260..9be99ee3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,4 +45,5 @@ dependencies { implementation 'com.github.hotchemi:permissionsdispatcher:2.2.0' annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:2.2.0" implementation files('libs/connectiq.jar') + implementation 'org.nanohttpd:nanohttpd:2.3.1' } diff --git a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java index a47cf4a7..b8b45322 100644 --- a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java +++ b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java @@ -1,5 +1,6 @@ package com.cooper.wheellog; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Timer; @@ -32,6 +33,11 @@ import com.garmin.android.connectiq.exception.ServiceUnavailableException; import com.garmin.monkeybrains.serialization.MonkeyHash; +import org.json.JSONException; +import org.json.JSONObject; + +import fi.iki.elonen.NanoHTTPD; + import static com.cooper.wheellog.utils.Constants.ACTION_REQUEST_KINGSONG_HORN; @@ -42,23 +48,29 @@ public class GarminConnectIQ extends Service implements IQApplicationInfoListene public enum MessageType { EUC_DATA, PLAY_HORN, + HTTP_READY, } - public static final int MESSAGE_KEY_MSG_TYPE = -2; - public static final int MESSAGE_KEY_MSG_DATA = -1; - public static final int MESSAGE_KEY_SPEED = 0; - public static final int MESSAGE_KEY_BATTERY = 1; - public static final int MESSAGE_KEY_TEMPERATURE = 2; - public static final int MESSAGE_KEY_FAN_STATE = 3; - public static final int MESSAGE_KEY_BT_STATE = 4; - public static final int MESSAGE_KEY_VIBE_ALERT = 5; - public static final int MESSAGE_KEY_USE_MPH = 6; - public static final int MESSAGE_KEY_MAX_SPEED = 7; - public static final int MESSAGE_KEY_RIDE_TIME = 8; - public static final int MESSAGE_KEY_DISTANCE = 9; - public static final int MESSAGE_KEY_TOP_SPEED = 10; - public static final int MESSAGE_KEY_READY = 11; - public static final int MESSAGE_KEY_POWER = 12; + public static final int MESSAGE_KEY_MSG_TYPE = -2; + public static final int MESSAGE_KEY_MSG_DATA = -1; + public static final int MESSAGE_KEY_SPEED = 0; + public static final int MESSAGE_KEY_BATTERY = 1; + public static final int MESSAGE_KEY_TEMPERATURE = 2; + public static final int MESSAGE_KEY_FAN_STATE = 3; + public static final int MESSAGE_KEY_BT_STATE = 4; + public static final int MESSAGE_KEY_VIBE_ALERT = 5; + public static final int MESSAGE_KEY_USE_MPH = 6; + public static final int MESSAGE_KEY_MAX_SPEED = 7; + public static final int MESSAGE_KEY_RIDE_TIME = 8; + public static final int MESSAGE_KEY_DISTANCE = 9; + public static final int MESSAGE_KEY_TOP_SPEED = 10; + public static final int MESSAGE_KEY_READY = 11; + public static final int MESSAGE_KEY_POWER = 12; + public static final int MESSAGE_KEY_ALARM1_SPEED = 13; + public static final int MESSAGE_KEY_ALARM2_SPEED = 14; + public static final int MESSAGE_KEY_ALARM3_SPEED = 15; + + public static final int MESSAGE_KEY_HTTP_PORT = 99; int lastSpeed = 0; int lastBattery = 0; @@ -81,10 +93,16 @@ public enum MessageType { private IQDevice mDevice; private IQApp mMyApp; + private GarminConnectIQWebServer mWebServer; + public static boolean isInstanceCreated() { return instance != null; } + public static GarminConnectIQ instance() { + return instance; + } + @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind"); @@ -121,6 +139,8 @@ public void onDestroy() { // so no worries. } + stopWebServer(); + instance = null; } @@ -239,7 +259,6 @@ private void refreshData() { lastConnectionState = WheelData.getInstance().isConnected(); data.put(MESSAGE_KEY_BT_STATE, lastConnectionState); - // TODO: hey I should actually make the watch vibrate at a certain speed! data.put(MESSAGE_KEY_VIBE_ALERT, false); data.put(MESSAGE_KEY_USE_MPH, SettingsUtil.isUseMPH(GarminConnectIQ.this)); data.put(MESSAGE_KEY_MAX_SPEED, SettingsUtil.getMaxSpeed(GarminConnectIQ.this)); @@ -318,19 +337,25 @@ public void onDeviceStatusChanged(IQDevice device, IQDeviceStatus status) { switch(status.name()) { case "CONNECTED": - startRefreshTimer(); + // Disabled the push method for now until a dev from garmin can shed some light on the + // intermittent FAILURE_DURING_TRANSFER that we have seen. This is documented here: + // https://forums.garmin.com/developer/connect-iq/f/legacy-bug-reports/5144/failure_during_transfer-issue-again-now-using-comm-sample + // startRefreshTimer(); + + // As a workaround, start a nanohttpd server that will listen for data requests from the watch. This is + // also documented on the link above and is apparently a good workaround for the meantime. In our implementation + // we instanciate the httpd server on an ephemeral port and send a message to the watch to tell it on which port + // it can request its data. + startWebServer(); break; case "NOT_PAIRED": case "NOT_CONNECTED": case "UNKNOWN": cancelRefreshTimer(); + stopWebServer(); } - - // TODO: make sure the device passed matches the one that's selected - // mStatusText.setText(status.name()); } - // IQApplicationEventListener @Override public void onMessageReceived(IQDevice device, IQApp app, List message, IQMessageStatus status) { @@ -409,4 +434,101 @@ public void onCompletion(MediaPlayer mp) { }); } } + + public void startWebServer() { + Log.d(TAG,"startWebServer"); + + if (mWebServer != null) + return; + + try { + mWebServer = new GarminConnectIQWebServer(); + Log.d(TAG, "port is:" + mWebServer.getListeningPort()); + + HashMap data = new HashMap(); + data.put(MESSAGE_KEY_HTTP_PORT, mWebServer.getListeningPort()); + + HashMap message = new HashMap(); + message.put(MESSAGE_KEY_MSG_TYPE, MessageType.HTTP_READY.ordinal()); + message.put(MESSAGE_KEY_MSG_DATA, new MonkeyHash(data)); + + try { + mConnectIQ.sendMessage(mDevice, mMyApp, message, new IQSendMessageListener() { + + @Override + public void onMessageStatus(IQDevice device, IQApp app, IQMessageStatus status) { + Log.d(TAG, "message status: " + status.name()); + + if (status.name() != "SUCCESS") + Toast.makeText(GarminConnectIQ.this, status.name(), Toast.LENGTH_LONG).show(); + } + + }); + } catch (InvalidStateException e) { + Log.e(TAG, "ConnectIQ is not in a valid state"); + Toast.makeText(this, "ConnectIQ is not in a valid state", Toast.LENGTH_LONG).show(); + } catch (ServiceUnavailableException e) { + Log.e(TAG, "ConnectIQ service is unavailable. Is Garmin Connect Mobile installed and running?"); + Toast.makeText(this, "ConnectIQ service is unavailable. Is Garmin Connect Mobile installed and running?", Toast.LENGTH_LONG).show(); + } + + } catch (IOException e) { + } + } + + public void stopWebServer() { + Log.d(TAG,"stopWebServer"); + if (mWebServer != null) { + mWebServer.stop(); + mWebServer = null; + } + } } + +class GarminConnectIQWebServer extends NanoHTTPD { + public GarminConnectIQWebServer()throws IOException { + super( 8080); // 0 to automatically find an available ephemeral port + start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); + } + + @Override + public Response serve(IHTTPSession session) + { + if (session.getMethod() == Method.GET && session.getUri().equals("/data")) { + return handleData(); + } + + return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found."); + } + + private Response handleData() { + Log.d("GarminConnectIQWebSe...","handleData"); + JSONObject data = new JSONObject(); + + try { + data.put("" + GarminConnectIQ.MESSAGE_KEY_SPEED, WheelData.getInstance().getSpeed() / 10); // Convert to km/h + data.put("" + GarminConnectIQ.MESSAGE_KEY_BATTERY, WheelData.getInstance().getBatteryLevel()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_TEMPERATURE, WheelData.getInstance().getTemperature()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_FAN_STATE, WheelData.getInstance().getFanStatus()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_BT_STATE, WheelData.getInstance().isConnected()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_VIBE_ALERT, false); + data.put("" + GarminConnectIQ.MESSAGE_KEY_USE_MPH, SettingsUtil.isUseMPH(GarminConnectIQ.instance())); + data.put("" + GarminConnectIQ.MESSAGE_KEY_MAX_SPEED, SettingsUtil.getMaxSpeed(GarminConnectIQ.instance())); + data.put("" + GarminConnectIQ.MESSAGE_KEY_RIDE_TIME, WheelData.getInstance().getRideTime()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_DISTANCE, WheelData.getInstance().getDistance() / 100); + data.put("" + GarminConnectIQ.MESSAGE_KEY_TOP_SPEED, WheelData.getInstance().getTopSpeed() / 10); + data.put("" + GarminConnectIQ.MESSAGE_KEY_POWER, (int) WheelData.getInstance().getPowerDouble()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_ALARM1_SPEED, WheelData.getInstance().getKSAlarm1Speed()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_ALARM2_SPEED, WheelData.getInstance().getKSAlarm2Speed()); + data.put("" + GarminConnectIQ.MESSAGE_KEY_ALARM3_SPEED, WheelData.getInstance().getKSAlarm3Speed()); + + JSONObject message = new JSONObject(); + message.put("" + GarminConnectIQ.MESSAGE_KEY_MSG_TYPE, GarminConnectIQ.MessageType.EUC_DATA.ordinal()); + message.put("" + GarminConnectIQ.MESSAGE_KEY_MSG_DATA, data); + + return newFixedLengthResponse(Response.Status.OK, "application/json", message.toString()); + } catch (JSONException e) { + return newFixedLengthResponse(Response.Status.SERVICE_UNAVAILABLE, "text/plain", ""); + } + } +} \ No newline at end of file From 507e76b0634226fbfbff55e4c2db591fcaf0129f Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Wed, 7 Aug 2019 23:24:39 -0400 Subject: [PATCH 31/34] Moving nanohttpd to localhost + ephemeral port --- app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java index b8b45322..5df7a4e2 100644 --- a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java +++ b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java @@ -487,7 +487,7 @@ public void stopWebServer() { class GarminConnectIQWebServer extends NanoHTTPD { public GarminConnectIQWebServer()throws IOException { - super( 8080); // 0 to automatically find an available ephemeral port + super("localhost", 0); // 0 to automatically find an available ephemeral port start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } From 7f5df1272fccddb15aa0c5b4a80b8ee36485d7ac Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Mon, 19 Aug 2019 10:49:39 -0400 Subject: [PATCH 32/34] Added a featureflag for nanohttpd method requiring garmin connect 4.22 (not out yet) --- .../java/com/cooper/wheellog/GarminConnectIQ.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java index 5df7a4e2..87c9a902 100644 --- a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java +++ b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java @@ -45,6 +45,10 @@ public class GarminConnectIQ extends Service implements IQApplicationInfoListene public static final String TAG = GarminConnectIQ.class.getSimpleName(); public static final String APP_ID = "df8bf0ab-1828-4037-a328-ee86d29d0501"; + // This will require Garmin Connect V4.22 + // https://forums.garmin.com/developer/connect-iq/i/bug-reports/connect-version-4-20-broke-local-http-access + public static final boolean FEATURE_FLAG_NANOHTTPD = false; + public enum MessageType { EUC_DATA, PLAY_HORN, @@ -340,13 +344,17 @@ public void onDeviceStatusChanged(IQDevice device, IQDeviceStatus status) { // Disabled the push method for now until a dev from garmin can shed some light on the // intermittent FAILURE_DURING_TRANSFER that we have seen. This is documented here: // https://forums.garmin.com/developer/connect-iq/f/legacy-bug-reports/5144/failure_during_transfer-issue-again-now-using-comm-sample - // startRefreshTimer(); + if (!FEATURE_FLAG_NANOHTTPD) { + startRefreshTimer(); + } // As a workaround, start a nanohttpd server that will listen for data requests from the watch. This is // also documented on the link above and is apparently a good workaround for the meantime. In our implementation // we instanciate the httpd server on an ephemeral port and send a message to the watch to tell it on which port // it can request its data. - startWebServer(); + if (FEATURE_FLAG_NANOHTTPD) { + startWebServer(); + } break; case "NOT_PAIRED": case "NOT_CONNECTED": From 35635b6e611b923f6d161a37ca425a26e661ae0d Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Thu, 22 Aug 2019 21:02:54 -0400 Subject: [PATCH 33/34] Garmin Connect v4.22 is now out, enabling nanohttpd --- app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java index 87c9a902..5193afea 100644 --- a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java +++ b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java @@ -47,7 +47,7 @@ public class GarminConnectIQ extends Service implements IQApplicationInfoListene // This will require Garmin Connect V4.22 // https://forums.garmin.com/developer/connect-iq/i/bug-reports/connect-version-4-20-broke-local-http-access - public static final boolean FEATURE_FLAG_NANOHTTPD = false; + public static final boolean FEATURE_FLAG_NANOHTTPD = true; public enum MessageType { EUC_DATA, From 4d7f372dd365ff7dd7ffa4ccca73c0bec0371174 Mon Sep 17 00:00:00 2001 From: Marc Vieira-Cardinal Date: Thu, 22 Aug 2019 23:18:24 -0400 Subject: [PATCH 34/34] GCIQ whitelists 127.0.0.1, not localhost --- app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java index 5193afea..9a928b1e 100644 --- a/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java +++ b/app/src/main/java/com/cooper/wheellog/GarminConnectIQ.java @@ -495,7 +495,7 @@ public void stopWebServer() { class GarminConnectIQWebServer extends NanoHTTPD { public GarminConnectIQWebServer()throws IOException { - super("localhost", 0); // 0 to automatically find an available ephemeral port + super("127.0.0.1", 0); // 0 to automatically find an available ephemeral port start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); }