Compare commits

..

26 Commits

Author SHA1 Message Date
Dennis Guse 0605dbb0ab Release: v4.3.1 2023-04-07 18:11:52 +02:00
Dennis Guse bf03de6be7 Bugfix: GPS disabled warning was showing (repeatedly but incorrectly).
Fixes #1429.
2023-04-07 11:13:57 +02:00
Dennis Guse d65ce361f7 Upgrade gradlew to 8.0.2. 2023-04-06 07:37:43 +02:00
Dennis Guse 3f600f6be7 Voice announcements: added setting to skip announcements on the device's internal speaker.
Fixes #1392.
2023-04-05 18:26:29 +02:00
dependabot[bot] 9aab3c216c Bump com.android.tools:desugar_jdk_libs from 2.0.2 to 2.0.3
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

---
updated-dependencies:
- dependency-name: com.android.tools:desugar_jdk_libs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-03 19:25:16 +02:00
Dennis Guse 7935185812 UI: split stats_item between generic and sensor (some bugfixes).
Part of #24.
2023-03-29 21:50:42 +02:00
Dennis Guse 4c1b1be96c UI: split stats_item between generic and sensor.
Part of #24.
2023-03-28 21:08:46 +02:00
Dennis Guse 5762830655 Merge pull request #1461 from OpenTracksApp/refactorSensor
Refactor: unify the internal sensor handling.
2023-03-28 21:08:08 +02:00
Dennis Guse 6d61206782 Renamed GPSHandler to GPSManager. 2023-03-28 20:58:23 +02:00
Dennis Guse b9dedef292 Refactor: introduced SensorManager. 2023-03-28 20:58:23 +02:00
Dennis Guse f3caecd049 Refactor: rename BluetoothConnectionManager. 2023-03-27 22:59:31 +02:00
Dennis Guse e16780277f Refactor: introduced SensorConnector interface. 2023-03-27 22:59:31 +02:00
Dennis Guse 13484685a3 Merge remote-tracking branch 'weblate/main' 2023-03-25 09:06:27 +01:00
plum7x 7b745d9837 Update strings.xml 2023-03-17 07:41:32 +01:00
plum7x 52742064f6 Updated traditional Chinese translation (#1498) 2023-03-14 20:50:51 +01:00
josé m 2edf5582aa Translated using Weblate (Galician)
Currently translated at 100.0% (505 of 505 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/gl/
2023-03-11 18:37:57 +01:00
dependabot[bot] 808eb92bf2 Bump org.mockito:mockito-android from 4.11.0 to 5.2.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 4.11.0 to 5.2.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.11.0...v5.2.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-10 06:13:23 +01:00
Dennis Guse c0b84c3d72 Release: v4.3.0 2023-03-09 05:20:22 +01:00
Miguel Almeida e275d3e5c0 Enable backups if client side encryption is enabled 2023-03-08 23:12:26 +01:00
Dennis Guse b4ebad2c3c Merge remote-tracking branch 'weblate/main' 2023-03-07 21:25:56 +01:00
dependabot[bot] 1ab76e1317 Bump com.android.tools.build:gradle from 7.4.1 to 7.4.2
Bumps com.android.tools.build:gradle from 7.4.1 to 7.4.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-28 06:04:48 -05:00
nobkd 3d1104dd0b remove typo 2023-02-17 10:44:41 -05:00
dependabot[bot] 2df45d31de Bump androidx.appcompat:appcompat from 1.6.0 to 1.6.1
Bumps androidx.appcompat:appcompat from 1.6.0 to 1.6.1.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-12 14:06:47 +01:00
Allan Nordhøy 5436981e0d Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.4% (492 of 505 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2023-02-10 07:38:30 +01:00
dependabot[bot] fd2b1bec7d Bump com.android.tools:desugar_jdk_libs from 2.0.1 to 2.0.2
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

---
updated-dependencies:
- dependency-name: com.android.tools:desugar_jdk_libs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 12:18:53 +01:00
elastic rock bb1eaa5b56 Android system per-app language preferences 2023-02-05 00:58:21 +01:00
46 changed files with 952 additions and 321 deletions
+1 -1
View File
@@ -150,7 +150,7 @@ The developer documentation is in [README_API.md](README_API.md).
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
_My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
+9 -6
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
@@ -59,8 +59,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5141
versionName "v4.2.2"
versionCode 5168
versionName "v4.3.1"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -69,6 +69,8 @@ android {
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
resConfigs "ar", "bg", "ca", "cs", "da", "de", "el", "en", "es", "et", "eu", "fa", "fi", "fr", "ga", "gl", "hi", "hr", "hu", "in", "it", "iw", "ja", "ko", "lt", "lv", "mo", "ms", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh"
}
signingConfigs {
nightly {
@@ -120,22 +122,23 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-splashscreen:1.0.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'org.mockito:mockito-android:4.11.0'
androidTestImplementation 'org.mockito:mockito-android:5.2.0'
androidTestUtil 'androidx.test:orchestrator:1.4.2'
}
@@ -0,0 +1,5 @@
v4.3.0: OpenTracks
Changes:
- Allow backups (incl. encrypted to the Google)
- Android 13: allow app-specific language settings
@@ -0,0 +1,8 @@
v4.3.1: OpenTracks
Changes:
- Setting to skip voice announcements on device speaker
Bugfix:
- GPS disabled warning was falsely shown
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+6
View File
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+8 -6
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 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
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
@@ -166,7 +166,7 @@ public class ExportImportTest {
trackPointCreator.setClock("2020-02-02T02:02:18Z");
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
service.endCurrentTrack();
trackPointCreator.setClock("2020-02-02T02:03:20Z");
@@ -179,7 +179,7 @@ public class ExportImportTest {
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
trackPointCreator.setClock("2020-02-02T02:03:24Z");
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
service.endCurrentTrack();
Track track = contentProviderUtils.getTrack(trackId);
@@ -521,12 +521,12 @@ public class ExportImportTest {
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
});
trackPointCreator.setRemoteSensorManager(remoteSensorManager);
trackPointCreator.getSensorManager().setBluetoothSensorManager(remoteSensorManager);
trackPointCreator.onChange(new SensorDataSet());
}
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
AltitudeSumManager altitudeSumManager = trackPointCreator.getSensorManager().getAltitudeSumManager();
altitudeSumManager.setAltitudeGain_m(altitudeGain);
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
}
@@ -45,6 +45,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Tests insert location.
* Note: on API23, the TrackRecordingService may not be stopped properly before the next test.
* So, if something fails, subsequent tests may be affected.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestRecording {
@@ -112,7 +114,7 @@ public class TrackRecordingServiceTestRecording {
String startTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// then
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
@@ -149,7 +151,7 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String pauseTime = "2020-02-02T02:02:03Z";
@@ -195,7 +197,7 @@ public class TrackRecordingServiceTestRecording {
String starTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(starTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
String pauseTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(pauseTime);
@@ -227,7 +229,7 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
String stopTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(stopTime);
@@ -237,7 +239,7 @@ public class TrackRecordingServiceTestRecording {
String resumeTime = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(resumeTime);
service.resumeTrack(trackId);
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// then
new TrackPointAssert().assertEquals(List.of(
@@ -258,12 +260,13 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
String sensor3 = "2020-02-02T02:02:13Z";
@@ -297,7 +300,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
@@ -371,7 +374,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -436,7 +439,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -500,7 +503,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -564,7 +567,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -652,7 +655,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -702,7 +705,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -748,7 +751,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -812,8 +815,8 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
@@ -1,17 +1,12 @@
package de.dennisguse.opentracks.services;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import androidx.test.rule.ServiceTestRule;
import org.mockito.Mockito;
import java.time.Clock;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -24,15 +19,6 @@ public class TrackRecordingServiceTestUtils {
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
// Let's use default values.
PreferencesUtils.clear();
// Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
service.getTrackPointCreator().setClock(Clock.systemUTC());
service.endCurrentTrack();
}
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class GPSHandlerTest {
public class GPSManagerTest {
private final Context context = ApplicationProvider.getApplicationContext();
@@ -34,7 +34,7 @@ public class GPSHandlerTest {
private TrackPointCreator trackPointCreator;
@InjectMocks
private GPSHandler locationHandler;
private GPSManager locationHandler;
@BeforeClass
public static void preSetUp() {
@@ -50,7 +50,7 @@ public class GPSHandlerTest {
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
locationHandler.onStart(context, new Handler());
locationHandler.start(context, new Handler());
}
/**
@@ -25,7 +25,7 @@ public class TrackPointCreatorTest {
private TrackPointCreator.Callback server;
@Mock
private GPSHandler locationHandler;
private GPSManager locationHandler;
private TrackPointCreator subject;
+5 -2
View File
@@ -62,13 +62,16 @@ limitations under the License.
<application
android:name=".Startup"
android:allowBackup="false"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules_30_lower"
android:dataExtractionRules="@xml/backup_rules_31_higher"
android:hardwareAccelerated="true"
android:hasFragileUserData="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/ThemeCustom">
android:theme="@style/ThemeCustom"
android:localeConfig="@xml/locales_config">
<activity
android:name=".publicapi.StartRecording"
@@ -381,13 +381,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
snackbar.dismiss();
boolean snackbarShowing = snackbar != null && snackbar.isShown();
if (gpsStatusValue.isGpsStarted()) {
if (snackbarShowing) {
snackbar.dismiss();
}
return;
}
if (gpsStatusValue != GpsStatusValue.GPS_DISABLED) {
if (snackbarShowing) {
return;
}
snackbar = Snackbar
.make(viewBinding.trackRecordingCoordinatorLayout,
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
@@ -162,7 +162,7 @@ public class StatisticsRecordingFragment extends Fragment {
viewBinding.statsLayout.addView(viewHolder.getView(), param);
} catch (Exception e) {
throw new RuntimeException(e);
throw new RuntimeException("Could not add " + dataField.getKey(), e);
}
}
}
@@ -40,9 +40,9 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorData;
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
* Also parses the transferred data into {@link SensorDataObserver}.
*/
public abstract class BluetoothConnectionManager<DataType> {
public abstract class AbstractBluetoothConnectionManager<DataType> {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
private final SensorDataObserver observer;
@@ -136,12 +136,12 @@ public abstract class BluetoothConnectionManager<DataType> {
}
};
BluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
AbstractBluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
this.observer = observer;
}
BluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
this.serviceMeasurementUUIDs = serviceUUUID;
this.observer = observer;
}
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
*/
public class AltitudeSumManager implements SensorEventListener {
public class AltitudeSumManager implements SensorConnector, SensorEventListener {
private static final String TAG = AltitudeSumManager.class.getSimpleName();
@@ -8,11 +8,11 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnectionManager<Cadence> {
public class BluetoothConnectionManagerCyclingCadence extends AbstractBluetoothConnectionManager<Cadence> {
private static final String TAG = BluetoothRemoteSensorManagerCyclingCadence.class.getSimpleName();
private static final String TAG = BluetoothConnectionManagerCyclingCadence.class.getSimpleName();
BluetoothRemoteSensorManagerCyclingCadence(SensorDataObserver observer) {
BluetoothConnectionManagerCyclingCadence(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_CADENCE, observer);
}
@@ -5,9 +5,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
public class BluetoothConnectionManagerCyclingDistanceSpeed extends AbstractBluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
BluetoothRemoteSensorManagerCyclingDistanceSpeed(SensorDataObserver observer) {
BluetoothConnectionManagerCyclingDistanceSpeed(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
}
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectionManager<Power> {
public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothConnectionManager<Power> {
BluetoothRemoteSensorManagerCyclingPower(@NonNull SensorDataObserver observer) {
BluetoothConnectionManagerCyclingPower(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_POWER, observer);
}
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionManager<HeartRate> {
public class BluetoothConnectionManagerHeartRate extends AbstractBluetoothConnectionManager<HeartRate> {
BluetoothRemoteSensorManagerHeartRate(@NonNull SensorDataObserver observer) {
BluetoothConnectionManagerHeartRate(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEARTRATE, observer);
}
@@ -6,9 +6,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
public class BluetoothConnectionRunningSpeedAndCadence extends AbstractBluetoothConnectionManager<SensorDataRunning.Data> {
BluetoothRemoteSensorManagerRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
BluetoothConnectionRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
}
@@ -52,7 +52,7 @@ import de.dennisguse.opentracks.util.PermissionRequester;
*
* @author Sandor Dornbush
*/
public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.SensorDataObserver {
public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBluetoothConnectionManager.SensorDataObserver {
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
@@ -65,15 +65,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private Distance preferenceWheelCircumference;
private final BluetoothRemoteSensorManagerHeartRate heartRate = new BluetoothRemoteSensorManagerHeartRate(this);
private final BluetoothRemoteSensorManagerCyclingCadence cyclingCadence = new BluetoothRemoteSensorManagerCyclingCadence(this);
private final BluetoothRemoteSensorManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothRemoteSensorManagerCyclingDistanceSpeed(this);
private final BluetoothRemoteSensorManagerCyclingPower cyclingPower = new BluetoothRemoteSensorManagerCyclingPower(this);
private final BluetoothRemoteSensorManagerRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothRemoteSensorManagerRunningSpeedAndCadence(this);
private final BluetoothConnectionManagerHeartRate heartRate = new BluetoothConnectionManagerHeartRate(this);
private final BluetoothConnectionManagerCyclingCadence cyclingCadence = new BluetoothConnectionManagerCyclingCadence(this);
private final BluetoothConnectionManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManagerCyclingDistanceSpeed(this);
private final BluetoothConnectionManagerCyclingPower cyclingPower = new BluetoothConnectionManagerCyclingPower(this);
private final BluetoothConnectionRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionRunningSpeedAndCadence(this);
private final SensorDataSet sensorDataSet = new SensorDataSet();
private final SensorDataSetChangeObserver observer;
private final SensorManager.SensorDataSetChangeObserver observer;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
@@ -114,21 +114,23 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
}
};
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorDataSetChangeObserver observer) {
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorManager.SensorDataSetChangeObserver observer) {
this.context = context;
this.handler = handler;
this.observer = observer;
bluetoothAdapter = BluetoothUtils.getAdapter(context);
}
public void start() {
@Override
public void start(Context context, Handler handler) {
started = true;
//Registering triggers connection startup
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public synchronized void stop() {
@Override
public synchronized void stop(Context context) {
heartRate.disconnect();
cyclingCadence.disconnect();
cyclingSpeed.disconnect();
@@ -145,7 +147,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
private synchronized void connect(AbstractBluetoothConnectionManager<?> connectionManager, String address) {
if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled.");
return;
@@ -230,8 +232,4 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public Handler getHandler() {
return handler;
}
public interface SensorDataSetChangeObserver {
void onChange(SensorDataSet sensorDataSet);
}
}
@@ -0,0 +1,11 @@
package de.dennisguse.opentracks.sensors;
import android.content.Context;
import android.os.Handler;
public interface SensorConnector {
void start(Context context, Handler handler);
void stop(Context context);
}
@@ -0,0 +1,85 @@
package de.dennisguse.opentracks.sensors;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
public class SensorManager {
private static final String TAG = SensorManager.class.getSimpleName();
private BluetoothRemoteSensorManager bluetoothSensorManager;
private AltitudeSumManager altitudeSumManager;
public SensorManager(Context context, Handler handler, SensorDataSetChangeObserver observer) {
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, observer);
altitudeSumManager = new AltitudeSumManager();
}
public void start(Context context, Handler handler) {
bluetoothSensorManager.start(context, handler);
altitudeSumManager.start(context, handler);
}
public void stop(Context context) {
if (bluetoothSensorManager != null) {
bluetoothSensorManager.stop(context);
bluetoothSensorManager = null;
}
if (altitudeSumManager != null) {
altitudeSumManager.stop(context);
altitudeSumManager = null;
}
}
public SensorDataSet fill(TrackPoint trackPoint) {
altitudeSumManager.fill(trackPoint);
return bluetoothSensorManager.fill(trackPoint);
}
public void reset() {
if (bluetoothSensorManager == null || altitudeSumManager == null) {
Log.d(TAG, "No recording running and no reset necessary.");
return;
}
bluetoothSensorManager.reset();
altitudeSumManager.reset();
}
@Deprecated
@VisibleForTesting
public BluetoothRemoteSensorManager getBluetoothSensorManager() {
return bluetoothSensorManager;
}
@Deprecated
@VisibleForTesting
public void setBluetoothSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.bluetoothSensorManager = remoteSensorManager;
}
@Deprecated
@VisibleForTesting
public AltitudeSumManager getAltitudeSumManager() {
return altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
this.altitudeSumManager = altitudeSumManager;
}
@Deprecated
public interface SensorDataSetChangeObserver {
void onChange(SensorDataSet sensorDataSet);
}
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.time.Instant;
@@ -32,16 +31,6 @@ public abstract class SensorData<T> {
this.time = time;
}
@NonNull
public String getSensorAddress() {
return sensorAddress;
}
@Nullable
public String getSensorName() {
return sensorName;
}
public String getSensorNameOrAddress() {
return sensorName != null ? sensorName : sensorAddress;
}
@@ -111,7 +111,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
egm2008CorrectionManager = new EGM2008CorrectionManager();
trackRecordingManager = new TrackRecordingManager(this);
trackRecordingManager.start();
trackPointCreator = new TrackPointCreator(this);
trackPointCreator = new TrackPointCreator(this, this, handler);
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -177,7 +177,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
public void resumeTrack(Track.Id trackId) {
trackPointCreator.reset();
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
Log.w(TAG, "Cannot resume a non-existing track.");
return;
@@ -328,7 +327,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
TrackPoint trackPoint = data.second.first;
egm2008CorrectionManager.correctAltitude(this, trackPoint);
localVoiceAnnouncementManager.update(data.first);
localVoiceAnnouncementManager.update(this, data.first);
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
}
@@ -15,12 +15,14 @@
*/
package de.dennisguse.opentracks.services.announcement;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.mediarouter.media.MediaRouter;
import java.time.Duration;
@@ -73,12 +75,20 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
updateNextTaskDistance();
}
public void update(@NonNull Track track) {
public void update(@NonNull Context context, @NonNull Track track) {
if (voiceAnnouncement == null) {
Log.e(TAG, "Cannot update when in status shutdown.");
return;
}
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
&& MediaRouter.getInstance(context)
.getSelectedRoute()
.isDeviceSpeaker()) {
Log.i(TAG, "No voice announcement on device speaker.");
return;
}
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
@@ -20,14 +20,15 @@ import java.time.Instant;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.SensorConnector;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
private final String TAG = GPSHandler.class.getSimpleName();
private final String TAG = GPSManager.class.getSimpleName();
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
@@ -40,11 +41,11 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
private Duration gpsInterval;
private Distance thresholdHorizontalAccuracy;
public GPSHandler(TrackPointCreator trackPointCreator) {
public GPSManager(TrackPointCreator trackPointCreator) {
this.trackPointCreator = trackPointCreator;
}
public void onStart(@NonNull Context context, @NonNull Handler handler) {
public void start(@NonNull Context context, @NonNull Handler handler) {
this.context = context;
this.handler = handler;
@@ -61,7 +62,7 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
@SuppressWarnings({"MissingPermission"})
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
public void onStop() {
public void stop(Context context) {
if (locationManager != null && context != null) {
if (PermissionRequester.GPS.hasPermission(context)) {
LocationManagerCompat.removeUpdates(locationManager, this);
@@ -16,14 +16,13 @@ import java.time.ZoneId;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.SensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
/**
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
*/
public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDataSetChangeObserver {
public class TrackPointCreator implements SensorManager.SensorDataSetChangeObserver {
private static final String TAG = TrackPointCreator.class.getSimpleName();
@@ -34,45 +33,34 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
@NonNull
private Clock clock = new MonotonicClock();
private final GPSHandler gpsHandler;
private BluetoothRemoteSensorManager remoteSensorManager;
private AltitudeSumManager altitudeSumManager;
private final GPSManager gpsManager;
private SensorManager sensorManager;
public TrackPointCreator(Callback service) {
public TrackPointCreator(Callback service, Context context, Handler handler) {
this.service = service;
this.gpsHandler = new GPSHandler(this);
this.gpsManager = new GPSManager(this);
this.sensorManager = new SensorManager(context, handler, this);
}
@VisibleForTesting
TrackPointCreator(GPSHandler gpsHandler, Callback service) {
TrackPointCreator(GPSManager gpsManager, Callback service) {
this.service = service;
this.gpsHandler = gpsHandler;
this.gpsManager = gpsManager;
}
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
this.context = context;
gpsHandler.onStart(context, handler);
remoteSensorManager = new BluetoothRemoteSensorManager(context, handler, this);
altitudeSumManager = new AltitudeSumManager();
remoteSensorManager.start();
altitudeSumManager.start(context, handler);
gpsManager.start(context, handler);
sensorManager.start(context, handler);
}
private boolean isStarted() {
return context != null;
}
public synchronized void reset() {
if (remoteSensorManager == null || altitudeSumManager == null) {
Log.d(TAG, "No recording running and no reset necessary.");
return;
}
remoteSensorManager.reset();
altitudeSumManager.reset();
private synchronized void reset() {
sensorManager.reset();
}
private SensorDataSet addSensorData(TrackPoint trackPoint) {
@@ -80,31 +68,12 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
Log.w(TAG, "Not started, should not be called.");
return null;
}
SensorDataSet sensorDataSet = null;
BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager;
if (localRemoteSensorManager != null) {
sensorDataSet = localRemoteSensorManager.fill(trackPoint);
}
AltitudeSumManager localAltitudeSumManager = altitudeSumManager;
if (localAltitudeSumManager != null) {
localAltitudeSumManager.fill(trackPoint);
}
return sensorDataSet;
return sensorManager.fill(trackPoint);
}
public synchronized void stop() {
gpsHandler.onStop();
if (remoteSensorManager != null) {
remoteSensorManager.stop();
remoteSensorManager = null;
}
if (altitudeSumManager != null) {
altitudeSumManager.stop(context);
altitudeSumManager = null;
}
gpsManager.stop(context);
this.context = null;
}
@@ -125,7 +94,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
addSensorData(trackPoint);
boolean stored = service.newTrackPoint(trackPoint, gpsHandler.getThresholdHorizontalAccuracy());
boolean stored = service.newTrackPoint(trackPoint, gpsManager.getThresholdHorizontalAccuracy());
if (stored) {
reset();
}
@@ -169,26 +138,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
}
@VisibleForTesting
public AltitudeSumManager getAltitudeSumManager() {
return altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
this.altitudeSumManager = altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public BluetoothRemoteSensorManager getRemoteSensorManager() {
return remoteSensorManager;
}
@Deprecated
@VisibleForTesting
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.remoteSensorManager = remoteSensorManager;
public SensorManager getSensorManager() {
return sensorManager;
}
@VisibleForTesting
@@ -202,8 +153,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
}
@VisibleForTesting
public GPSHandler getGpsHandler() {
return gpsHandler;
public GPSManager getGpsHandler() {
return gpsManager;
}
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
@@ -302,6 +302,11 @@ public class PreferencesUtils {
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static boolean shouldVoiceAnnouncementOnDeviceSpeaker() {
final boolean DEFAULT = resources.getBoolean(R.bool.voice_on_device_speaker_default);
return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT);
}
public static Duration getVoiceAnnouncementFrequency() {
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
@@ -87,4 +87,14 @@ public class DataField implements Parcelable {
public int hashCode() {
return Objects.hash(key, isVisible, isPrimary, isWide);
}
@Override
public String toString() {
return "DataField{" +
"key='" + key + '\'' +
", isVisible=" + isVisible +
", isPrimary=" + isPrimary +
", isWide=" + isWide +
'}';
}
}
@@ -2,18 +2,15 @@ package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.util.StringUtils;
@@ -256,84 +253,4 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
}
}
public static class SensorHeartRate extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
//TODO Loads preference every time
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
int textColor;
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
sensorName = sensorDataSet.getHeartRate().second;
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
} else {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
textColor = zones.getTextColorForZone(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
getBinding().statsDescriptionSecondary.setText(sensorName);
getBinding().statsValue.setTextColor(textColor);
}
}
public static class SensorCadence extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
sensorName = sensorDataSet.getCadence().second;
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
public static class SensorPower extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
}
@@ -31,9 +31,9 @@ public class Mapping {
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), GenericStatisticsViewHolder.SensorHeartRate::new);
m.put(context.getString(R.string.stats_custom_layout_cadence_key), GenericStatisticsViewHolder.SensorCadence::new);
m.put(context.getString(R.string.stats_custom_layout_power_key), GenericStatisticsViewHolder.SensorPower::new);
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), SensorStatisticsViewHolder.SensorHeartRate::new);
m.put(context.getString(R.string.stats_custom_layout_cadence_key), SensorStatisticsViewHolder.SensorCadence::new);
m.put(context.getString(R.string.stats_custom_layout_power_key), SensorStatisticsViewHolder.SensorPower::new);
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
return m;
@@ -0,0 +1,108 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.databinding.StatsSensorItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.util.StringUtils;
public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<StatsSensorItemBinding> {
@Override
protected StatsSensorItemBinding createViewBinding(LayoutInflater inflater) {
return StatsSensorItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
public static class SensorHeartRate extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
sensorName = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
}
//TODO Loads preference every time
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
int textColor;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
} else {
textColor = zones.getTextColorForZone(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
getBinding().statsDescriptionSecondary.setText(sensorName);
getBinding().statsValue.setTextColor(textColor);
}
}
public static class SensorCadence extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
sensorName = sensorDataSet.getCadence().second;
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
public static class SensorPower extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/stats_value"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="90" />
<TextView
android:id="@+id/stats_unit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/stats_value"
tools:text="@string/sensor_unit_beats_per_minute" />
<TextView
android:id="@+id/stats_description_main"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_value"
tools:text="@string/sensor_state_heart_rate" />
<TextView
android:id="@+id/stats_description_secondary"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
tools:text="90" />
</androidx.constraintlayout.widget.ConstraintLayout>
+32 -31
View File
@@ -14,40 +14,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--><resources>
<string name="about_preference_title">Acerca de OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar as túas actividades deportivas que respecta a túa privacidade.</string>
<string name="about_preference_title">Sobre OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar actividades deportivas que respecta a túa privacidade.</string>
<string name="about_url">Web do proxecto: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Nome da versión: %1$s</string>
<string name="about_version_code">Código da versión: %1$d</string>
<string name="about_history">OpenTracks é un fork de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a>(abandonado en 2016).</string>
<string name="about_history">OpenTracks é unha bifurcación de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a> (abandonado en 2016).</string>
<string name="about_libraries">Bibliotecas</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks ten as seguintes funcións:</string>
<string name="help_feature_content">
* crea rutas GPS\n
* usa monitor do pulso cardíaco (só Bluetooth LE)\n
* anuncios por voz (de tempo ou distancia)\n
* crea marcadores (incl. fotos)\n
* funciona sen precisar conexión a rede\n
* mostra as rutas nun mapa (ver abaixo)
</string>
<string name="help_track_map_title">¿Como mostrar a ruta nun mapa?</string>
<string name="help_feature_title">Características de OpenTracks:</string>
<string name="help_feature_content">* crear rutas GPS
\n * usar monitor do pulso cardíaco (só Bluetooth LE)
\n * anuncios por voz (de tempo ou distancia)
\n * crear marcadores (incl. fotos)
\n * funciona sen precisar conexión á rede
\n * mostra as rutas nun mapa (ver abaixo)</string>
<string name="help_track_map_title">Como mostrar a ruta no mapa\?</string>
<string name="help_track_map_content"> Usa a icona do mapa pequeno.
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard"> OSMDashboard </a>. Ofrece mapas en liña ou fóra de liña a través de <a href="https://www.openstreetmap.org"> OpenStreetMap </a>.
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Proporciona mapas en liña ou sen conexión grazas a <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n En realidade, OSMDashboard é unha aplicación independente que só pode acceder (temporalmente) aos datos das pistas que se amosarán.
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i> ACTION_VIEW </i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">¿De que xeito garda OpenTracks as fotos tomadas como marcadores?</string>
<string name="help_storage_pictures_content">Gárdanse no almacenaxe interno da app (só accesible desde OpenTracks).</string>
<string name="help_track_export_title">¿De que xeito exporto/importo as rutas gravadas?</string>
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As ruts compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
<string name="help_data_google_play_services_title">¿OpenTracks precisa/utiliza Google Play Service?</string>
<string name="help_recording_title">¿Como grava OpenTracks os datos?</string>
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i>ACTION_VIEW</i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">De que xeito garda OpenTracks as fotos tomadas como marcadores\?</string>
<string name="help_storage_pictures_content">Gárdanse na almacenaxe interna da app (só accesible desde OpenTracks).</string>
<string name="help_track_export_title">De que xeito exporto/importo as rutas gravadas\?</string>
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
<string name="help_data_google_play_services_title">OpenTracks precisa/utiliza Google Play Service\?</string>
<string name="help_recording_title">Como grava OpenTracks os datos\?</string>
<string name="help_recording_content">
OpenTracks usa o sensor GPS do dispositivo para gravar a localización de xeito contínuo.
Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, elevación), OpenTracks non pode facer nada ao respecto.</string>
<string name="help_data_storage_title">De que xeito garda OpenTracks os datos dos sensores\?</string>
<string name="help_data_storage_content"> Os datos dos sensores gárdanse asociados á localización. Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). </string>
<string name="help_gps_troubleshooting_title">¿OpenTracks non grava as localizacións?</string>
<string name="help_gps_troubleshooting_title">OpenTracks non grava as localizacións\?</string>
<string name="help_gps_troubleshooting_content"> Podes intentar:
\n a) mira que o GPS esté activado,
\n b) mellora a recepción GPS (ex. sae ao exterior),
@@ -55,7 +53,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
\n d) desactiva o aforro de enerxía.
\n
\n Se isto non axuda, infórmanos do problema. </string>
<string name="help_support_title">¿Como podo axudar a OpenTracks?</string>
<string name="help_support_title">Como podo axudar a OpenTracks\?</string>
<string name="help_support_content"> Podes:
\n a) falar de OpenTracks coas túas amizades,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propoñer novas funcións</a>,
@@ -154,7 +152,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="description_recorded_time">Hora da gravación: %1$s</string>
<string name="description_speed_ms">Velocidade (m/s)</string>
<string name="description_sensor_cadence">Cadencia (rpm)</string>
<string name="description_sensor_heart_rate">Frecuencia cardíaca (pulsacións por minuto)</string>
<string name="description_sensor_heart_rate">Pulso cardíaco (latexos por minuto)</string>
<string name="description_sensor_power">Potencia (W)</string>
<string name="description_default_speed_or_pace">Por tipo de actividade</string>
<string name="description_speed_imperial">Velocidade (mi/h)</string>
@@ -233,8 +231,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="permission_gps_failed">OpenTracks precisa permiso para usar o GPS.</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_heart_rate">Frecuencia cardíaca</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_heart_rate">Pulso cardíaco</string>
<string name="sensor_state_heart_rate_value">%1$d lpm</string>
<string name="sensor_state_power">Potencia</string>
<string name="sensor_could_not_scan">Non se escanearon dispositivos Bluetooth (fallo %1$i).</string>
<string name="bluetooth_disabled">Activa o Bluetooth.</string>
@@ -508,10 +506,10 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
<string name="stats_clock">Reloxo</string>
<string name="settings_open_tracks_title">Open Tracks</string>
<string name="settings_defaults_summary">O Preferido Unidades, Por Defecto Actividade, Controlar O Nome Por Defecto</string>
<string name="settings_defaults_summary">Unidades preferidas, Actividade por defecto, Nome da ruta</string>
<string name="settings_announcements_summary">A Distancia, Tempo, Voz Velocidade</string>
<string name="settings_open_tracks_summary">Todo Sobre OpenTracks Proxecto</string>
<string name="settings_defaults_title">Por defecto Unidades e Actividade</string>
<string name="settings_defaults_title">Unidades e Actividade por defecto</string>
<string name="settings_ui_title">Interface da app</string>
<string name="settings_ui_summary">Aparencia, Gravación, Deseño, Tema</string>
<string name="settings_gps_title">GPS</string>
@@ -523,7 +521,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="settings_import_export_summary">Importación, Exportación, Auto Exportación</string>
<string name="settings_units_category">Unidades</string>
<string name="settings_activity_defaults_category">Valores por defecto</string>
<string name="settings_reset_summary">Axustar a Configuración de Valores por Defecto</string>
<string name="settings_reset_summary">Restablecer Axustes dos Valores</string>
<string name="menu_select_layout">Elixe disposición</string>
<string name="custom_layout_list_title">Disposicións</string>
<string name="custom_layout_list_edit_name">Escribe o nome da nova disposición</string>
@@ -576,8 +574,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks está gravando sen datos GPS.</string>
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
<string name="settings_announcements_lap_heart_rate">Ritmo cardíaco na volta</string>
<string name="settings_announcements_average_heart_rate">Ritmo cardíaco medio</string>
<string name="settings_announcements_lap_heart_rate">Pulso durante a volta</string>
<string name="settings_announcements_average_heart_rate">Pulso cardíaco medio</string>
<string name="image_discard">Descartar</string>
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo medio</string>
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo da volta</string>
@@ -611,4 +609,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="settings_public_api_package_title">Nome do paquete para Public API</string>
<string name="track_delete_number_of_tracks">%1$d rutas</string>
<string name="introduction_osm_dashboard">OpenTracks non proporciona un mapa. Instala OSMDashboard para ver as gravacións nun mapa.</string>
<string name="help_data_google_play_services_content">Non</string>
<string name="settings_sensor_heart_rate_max">Zonas: Pulso máximo (lpm)</string>
<string name="settings_api_summary">Control remoto e API de estatísticas para outras apps</string>
</resources>
+2
View File
@@ -603,4 +603,6 @@ limitations under the License.
<string name="settings_public_api_package_title">Pakkenavn for offentlig API</string>
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
<string name="help_data_google_play_services_content">Nei</string>
<string name="settings_sensor_heart_rate_max">Soner: Makspuls (bpm)</string>
<string name="settings_api_summary">Fjernkontroll- og statistikk-API for andre programmer</string>
</resources>
+423 -25
View File
@@ -1,5 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="about_preference_title">關於 OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string>
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
<string name="about_version_name">版本名稱:%1$s</string>
<string name="about_version_code">版本代號:%1$d</string>
<string name="about_history">OpenTracks 是一個 <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> 的分支 (於 2016 年停止維護)。</string>
<string name="about_third_party_data">第三方資料</string>
<string name="about_libraries">函式庫</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks 擁有以下特點:</string>
<string name="help_feature_content">
* 建立 GPS 蹤跡\n
* 使用一個心率感測器 (僅限藍牙 LE)\n
* 語音通知 (依時間或距離)\n
* 建立標記 (包含圖片)\n
* 完全離線工作\n
* 在地圖上顯示蹤跡 (見下文)
</string>
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
<string name="help_track_map_content">
使用小的地圖圖示。\n
a)
(即時更新) 安裝 <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>.
透過 <a href="https://www.openstreetmap.org">OpenStreetMap</a> 提供線上或離線地圖。\n
實際上,OSMDashboard 是一個單獨的應用程式,只能 (暫時) 存取將要顯示的蹤跡資料。
\n
b)
透過一個 "普通的" 地圖應用程式 (例如,<a href="http://osmand.net/">OsmAnd</a> 與 <a href="https://maps.me/">MAPS.ME</a>)。
這些 APP 需要支援 KMZ 檔案格式 (只有位置與時間戳記)。
事實上,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。
</string>
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。</string>
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
<string name="help_track_export_content">
可以從設定匯出蹤跡為 KMZ、KML 或 GPX。
KMZ/KML 全部資料;GPX 只有位置。
通過<i>分享圖示</i>以分享蹤跡為 KMZ。
</string>
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
<string name="help_data_google_play_services_content">否</string>
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
<string name="help_recording_content">
OpenTracks 使用裝置的 GPS 感測器以連續紀錄位置。
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
</string>
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
<string name="help_data_storage_content">
感測器資料儲存在每個記錄的位置。
就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。
如果您是站著不動或在室內,沒有感測器資料會被記錄 (但它會被顯示)。
</string>
<string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string>
<string name="help_gps_troubleshooting_content">
您能做什麼:\n
a) 檢查 GPS 是否啟用,\n
b) 改善 GPS 收訊 (例如,前往戶外),\n
c) 使用 <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app 檢查,以及\n
d) 禁用省電。\n
\n
如果一切都沒有幫助,請回報 bug。
</string>
<string name="help_support_title">我如何支持 OpenTracks?</string>
<string name="help_support_content">
您可以:\n
a) 推廣 OpenTracks,\n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">提出建議</a>,\n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">回報 bugs</a>,\n
d) 幫助軟體開發,\n
e) 在 <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play 商店</a> 購買它,\n
f) 贊助,以及\n
g) 做任何您認為對 OpenTracks 有幫助的事情。
</string>
<string name="open_track_as_KML">開啟為 KML</string>
<string name="open_track_as_GPX">開啟為 GPX</string>
<string name="open_track_as_trackfileformat">開啟為 %1$s</string>
<!-- Activity Type -->
<string name="activity_type_airplane">飛機</string>
<string name="activity_type_atv">越野車</string>
<string name="activity_type_biking">騎自行車</string>
@@ -14,6 +116,7 @@
<string name="activity_type_driving">正在開車</string>
<string name="activity_type_driving_bus">駕駛公車</string>
<string name="activity_type_driving_car">駕車</string>
<string name="activity_type_escooter">電動滑板車</string>
<string name="activity_type_ferry">渡輪碼頭</string>
<string name="activity_type_frisbee">飛盤</string>
<string name="activity_type_gliding">滑翔</string>
@@ -66,61 +169,131 @@
<string name="activity_type_walking">步行</string>
<string name="activity_type_water_skiing">滑水</string>
<string name="activity_type_wind_surfing">風帆</string>
<string name="activity_type_swimming">游泳</string>
<string name="activity_type_swimming_open">開放水域游泳</string>
<string name="activity_type_workout">訓練</string>
<!-- Description -->
<string name="description_activity_type">活動類型:%1$s</string>
<string name="description_average_moving_pace_in_minute">平均移動速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_moving_pace_in_minute">平均移動步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_moving_speed">平均移動速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_average_pace_in_minute">平均速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_pace_in_minute">平均步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_speed">平均速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_altitude_gain">爬升高度:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_altitude_loss">下降高度:%1$d m (%2$d ft)</string>
<string name="description_altitude_imperial">海拔高度 (英尺)</string>
<string name="description_altitude_metric">海拔高度 (公尺)</string>
<string name="description_fastest_pace_in_minute">最快速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_fastest_pace_in_minute">最高步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_max_altitude">最高海拔:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_max_speed">最快速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_max_speed">最高速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_min_altitude">最低海拔:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_moving_time">移動時間:%1$s</string>
<string name="description_pace_imperial">速度 (分鐘/英里)</string>
<string name="description_pace_metric">速度 (分鐘/公里)</string>
<string name="description_pace_imperial">步速 (分鐘/英里)</string>
<string name="description_pace_nautical">步速 (分鐘/海里)</string>
<string name="description_pace_metric">步速 (分鐘/公里)</string>
<string name="description_recorded_time">記錄時間:%1$s</string>
<string name="description_sensor_cadence">轉速 (rpm)</string>
<string name="description_speed_ms">速度 (公尺/秒)</string>
<string name="description_sensor_cadence">踏頻 (rpm)</string>
<string name="description_sensor_heart_rate">心率 (bpm)</string>
<string name="description_sensor_power">功率 (W)</string>
<string name="description_default_speed_or_pace">依照活動類型</string>
<string name="description_speed_imperial">速度 (英里/小時)</string>
<string name="description_speed_nautical">速度 (節)</string>
<string name="description_speed_metric">速度 (公里/小時)</string>
<string name="description_time">時間</string>
<string name="description_total_distance">總距離:%1$.2f 公里 (%2$.1f 英里)</string>
<string name="description_total_time">總時間:%1$s</string>
<string name="description_speed_source_gps">GPS</string>
<!-- Export -->
<string name="export_with_photos">包含照片</string>
<string name="export_without_photos">不包含照片</string>
<string name="export_error_post_workout">運動後匯出失敗,請檢查匯出目錄。</string>
<string name="export_cannot_write_to_dir">無法寫入匯出目錄。</string>
<string name="export_dir_not_writable">" (不可寫入!)"</string>
<string name="export_progress_message">正在匯出至 %1$s…</string>
<string name="export_track_already_exists_msg">蹤跡 %1$s 已存在於目標目錄之中。</string>
<string name="export_all_do_it_for_all">對所有衝突這麼做</string>
<string name="export_track_errors">蹤跡未匯出</string>
<string name="export_progress_summary_new_msg">新蹤跡已匯出</string>
<string name="export_progress_summary_overwrite_msg">蹤跡覆蓋</string>
<string name="export_progress_summary_skip_msg">蹤跡已略過</string>
<string name="export_progress_summary_errors_msg">錯誤</string>
<!-- External Storage -->
<!-- Generic -->
<string name="generic_add">新增</string>
<string name="generic_delete_selected_confirm_title">確定要刪除所選項目?</string>
<string name="generic_description">內容描述</string>
<string name="generic_description_line">內容描述:%1$s</string>
<string name="generic_name">名稱</string>
<string name="generic_name_line">名稱:%1$s</string>
<string name="generic_open_track">開啟蹤跡</string>
<string name="generic_recording">正在記錄…</string>
<string name="generic_save">儲存</string>
<string name="generic_tracks">足跡</string>
<string name="generic_tracks">蹤跡</string>
<string name="generic_overwrite">覆蓋</string>
<string name="generic_skip">略過</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">顯示錯誤</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">已完成,但有一個錯誤</item>
<item quantity="other">已完成,但有 %1$d 個錯誤</item>
</plurals>
<string name="generic_completed">完成</string>
<string name="generic_click_twice_cancel">再次點擊返回以取消</string>
<string name="generic_choose_an_option">選擇一個選項</string>
<string name="generic_undo">撤消</string>
<string name="generic_filter">篩選</string>
<string name="generic_from">從</string>
<string name="generic_to">至</string>
<string name="generic_today">今日</string>
<string name="generic_yesterday">昨日</string>
<string name="generic_dismiss">Dismiss</string>
<!-- Gps -->
<string name="gps_starting">正在啟用 GPS</string>
<string name="gps_wait_for_better_signal">正在等待更清晰的 GPS 訊號</string>
<string name="gps_wait_for_signal">正在等待 GPS 訊號</string>
<string name="gps_disabled_msg">GPS 禁用</string>
<string name="gps_fixed_and_ready">GPS 已修復並準備就緒</string>
<string name="gps_recording_status">%1$s:%2$s</string>
<string name="gps_recording_without_signal">OpenTracks 紀錄中,不包含 GPS 資料。</string>
<!-- Image -->
<string name="image_arrow">箭頭</string>
<string name="image_marker">標記</string>
<string name="image_marker_count">標記數</string>
<string name="image_photo_marker">相片標記</string>
<string name="image_record">記錄</string>
<string name="image_stop">停止</string>
<string name="image_track">足跡</string>
<string name="image_discard">Discard</string>
<string name="image_track">蹤跡</string>
<!-- Import -->
<string name="import_thread_interrupted">執行緒中斷</string>
<string name="import_unsupported_format">未支援的檔案格式</string>
<string name="import_parser_error">分析器錯誤:%1$s</string>
<string name="import_unable_to_import_file">無法匯入檔案:%1$s</string>
<string name="import_no_kml_file_found">在 KMZ 中沒有發現 doc.kml 檔案</string>
<string name="import_error_info">%1$s:%2$s</string>
<string name="import_error_list_dialog_title">檔案未匯入</string>
<string name="import_file_imported">檔案 %1$s 已匯入</string>
<string name="import_prevent_reimport">您已設定防止重新匯入蹤跡</string>
<string name="import_progress_message">正在從 %1$s 匯入檔案…</string>
<string name="import_progress_summary_ok_msg">檔案已匯入</string>
<string name="import_progress_summary_exists_msg">已經存在</string>
<string name="import_progress_summary_errors_msg">錯誤</string>
<!-- Marker -->
<string name="marker_add_error">無法插入標記,因為無法取得 GPS 訊號。請再試一次。</string>
<string name="marker_add_success">標記已插入</string>
<string name="marker_add_canceled">已取消,未新增任何標記</string>
<string name="marker_add_photo_canceled">已取消,未加入照片標記</string>
<string name="marker_delete_one_confirm_message">系統將永久刪除裝置上的這個標記</string>
<string name="marker_delete_one_confirm_title">確定要刪除標記?</string>
<string name="marker_delete_multiple_confirm_message">系統將永久刪除裝置上您所選取的這些標記</string>
<string name="marker_name_format">標記 %1$d</string>
<string name="marker_title">%1$d 個位置 (共 %2$d 個)</string>
<!-- Marker Detail -->
<!-- Marker Edit -->
<string name="marker_edit_marker_type">標記類型 (例如瀑布)</string>
<!-- Marker List -->
<string name="marker_list_empty_message">您沒有任何標記</string>
<!-- Menu -->
<string name="menu_aggregated_statistics">彙總的統計資料</string>
<string name="menu_delete">刪除</string>
<string name="menu_edit">編輯</string>
@@ -129,21 +302,83 @@
<string name="menu_import_all">全部匯入</string>
<string name="menu_insert_marker">插入標記</string>
<string name="menu_insert_photo">插入相片</string>
<string name="menu_insert_gallery_img">插入圖片庫圖片</string>
<string name="menu_markers">標記</string>
<string name="menu_search">搜尋</string>
<string name="menu_select_all">全選</string>
<string name="menu_settings">設定</string>
<string name="menu_share">分享</string>
<string name="menu_select_layout">選擇布局</string>
<string name="menu_show_on_map">在地圖上顯示</string>
<string name="menu_start_gps">啟動 GPS</string>
<string name="menu_stop_gps">關閉 GPS</string>
<string name="menu_voice_rate">語速</string>
<string name="menu_resume_track">還原蹤跡</string>
<string name="menu_add_custom_layout">新布局</string>
<string name="menu_filter">篩選</string>
<!-- OpenTracks -->
<string name="recording_service">記錄服務</string>
<string name="delete_service">刪除服務</string>
<!-- Permission -->
<string name="permission_gps_failed">OpenTracks 需要授權使用 GPS。</string>
<string name="permission_bluetooth_failed">OpenTracks 需要授權使用藍芽。</string>
<string name="permission_bluetooth_failed_rejected">您需要手動在系統設定中授權 OpenTracks 存取附近裝置。</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_cadence_avg">平均踏頻</string>
<string name="sensor_state_cadence_max">最大踏頻</string>
<string name="sensor_state_heart_rate">心率</string>
<string name="sensor_state_heart_rate_avg">平均心率</string>
<string name="sensor_state_heart_rate_max">最大心率</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_power">電源</string>
<string name="sensor_state_power">功率</string>
<string name="sensor_state_power_avg">平均功率</string>
<string name="sensor_could_not_scan">無法掃描藍牙裝置 (錯誤 %1$i).</string>
<string name="bluetooth_disabled">請啟用藍芽。</string>
<string name="settings_open_tracks_title">開啟蹤跡</string>
<string name="settings_open_tracks_summary">所有關於 OpenTracks 專案</string>
<string name="settings_defaults_title">預設單位與活動</string>
<string name="settings_defaults_summary">首選單位、預設活動、蹤跡預設名稱</string>
<string name="settings_ui_title">使用者介面</string>
<string name="settings_ui_summary">外觀、紀錄布局、主題</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_gps_summary">時間間隔、距離間隔、最大距離、精度</string>
<string name="settings_sensors_title">藍芽感測器</string>
<string name="settings_sensors_summary">自行車感測器、輪周、跑步感測器</string>
<string name="settings_announcements_title">語音通知</string>
<string name="settings_announcements_summary">時間、距離、語速</string>
<string name="settings_import_export_title">匯入/匯出</string>
<string name="settings_import_export_summary">匯入、匯出、自動匯出</string>
<string name="settings_units_category">單位</string>
<string name="settings_activity_defaults_category">活動的預設值</string>
<string name="settings_reset_summary">重置設定為預設值</string>
<string name="settings_layout_reset_key" translatable="false">設定布局重置</string>
<!-- Settings Advanced -->
<string name="settings_advanced">進階</string>
<string name="settings_default_trackfileformat">匯出/分享檔案格式</string>
<string name="settings_export_filename_title">檔名的格式</string>
<!-- Settings Chart -->
<string name="settings_chart_by_distance">依距離</string>
<string name="settings_chart_by_time">依時間</string>
<!-- Settings Recording -->
<string name="settings_recording_customize_layout_title">自訂您的紀錄布局</string>
<string name="settings_recording_customize_layout_select_columns">每列資料欄位</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">在鎖定畫面顯示統計資料</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">當紀錄時,在不解鎖裝置的情況下顯示統計資料。</string>
<string name="settings_recording_keep_screen_on_while_recording_title">維持螢幕開啟</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">記錄中,維持螢幕開啟。</string>
<string name="settings_recording_fullscreen_on_while_recording_title">全螢幕</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">記錄中,在全螢幕中呈現。</string>
<string name="settings_recording_default_activity_title">預設活動類型</string>
<string name="settings_recording_location_frequency_summary">每 %1$s記錄一次位置資訊</string>
<string name="settings_recording_max_recording_distance_summary">系統會在兩個地點之間的距離大於 %1$s時建立新的區段</string>
@@ -155,77 +390,214 @@
<string name="settings_recording_track_name_date_iso_8601_option">日期 (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">日期 (當地)</string>
<string name="settings_recording_track_name_number_option">數字</string>
<string name="settings_recording_track_name_title">預設足跡名稱</string>
<string name="settings_recording_track_name_title">預設蹤跡名稱</string>
<string name="settings_recording_idle_speed_title">怠速閾值</string>
<string name="settings_night_mode_title">界面主題</string>
<string name="settings_night_mode_option_system">系統</string>
<string name="settings_night_mode_option_day">日</string>
<string name="settings_night_mode_option_night">夜</string>
<string name="settings_announcements_totaltime_frequency">時間間隔</string>
<string name="settings_announcements_distance_frequency">距離間隔</string>
<string name="settings_announcements_average_heart_rate">平均心率</string>
<string name="settings_announcements_lap_heart_rate">單圈心率</string>
<string name="settings_announcements_average_speed_pace">平均速度/步速</string>
<string name="settings_announcements_lap_speed_pace">單圈速度/步速</string>
<!-- Custom Layout -->
<string name="custom_layout_list_title">布局</string>
<string name="custom_layout_list_edit_name">輸入要新增的佈局名稱</string>
<string name="custom_layout_list_edit_already_exists">布局的名稱已經存在</string>
<string name="custom_layout_list_layout_removed">佈局已移除</string>
<string name="custom_layout_select_layout">選擇布局</string>
<string name="stats_custom_layout_default_layout">預設布局</string>
<!-- Custom Layout Reset -->
<string name="settings_layout_reset">重置自訂布局</string>
<string name="settings_layout_reset_confirm_message">所有的布局設定將被還原為預設值。這不會刪除任何裝置上的蹤跡。</string>
<string name="settings_layout_reset_confirm_title">重置您的布局?</string>
<string name="settings_layout_reset_done">所有的布局設定已被還原為預設值</string>
<!-- Settings Reset -->
<string name="settings_all_reset">重置</string>
<string name="settings_reset_confirm_message">系統會將所有設定還原為預設值。</string>
<string name="settings_reset_confirm_title">確定要立即重設?</string>
<string name="settings_reset_done">所有設定已還原為預設值</string>
<!-- Settings Sensor -->
<string name="settings_generic_sensor">通用</string>
<string name="settings_cycling_sensor">騎自行車</string>
<string name="settings_running_sensor">跑步</string>
<string name="settings_sensor_bluetooth">藍牙</string>
<string name="settings_sensor_cycling_distance_speed">距離與速度</string>
<string name="settings_sensor_cycling_cadence">踏頻</string>
<string name="settings_sensor_running_speed_and_cadence">距離、速度與踏頻</string>
<string name="settings_sensor_wheel_circumference">輪周 (公釐)</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">僅顯示宣告所需服務的藍芽裝置</string>
<string name="settings_sensor_heart_rate_max">區間:最大心率 (bpm)</string>
<!-- Settings Stats -->
<string name="settings_stats_rate_title">偏好速率</string>
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
<string name="settings_stats_units_nautical">航海 (海里、英尺)</string>
<string name="settings_stats_units_title">慣用單位</string>
<string name="share_track_share_file_body">你應該會喜歡這個足跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看足跡。\n\n%1$s</string>
<string name="share_track_subject">我想跟你分享足跡資訊。</string>
<string name="stats_average_moving_pace">平均移動速度</string>
<!-- Settings public api -->
<string name="settings_api_title">公開 API</string>
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
<string name="settings_public_api_package_title">公開 API 的套件名稱</string>
<string name="settings_public_api_enabled_title">公開 API</string>
<string name="settings_public_api_enabled_summary_on">其他已安裝的應用程式可以開始或停止紀錄。</string>
<string name="settings_public_api_enabled_summary_off">只有 OpenTracks 可以開始或停止紀錄。</string>
<string name="settings_public_api_disabled_toast">OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。</string>
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</string>
<string name="settings_public_api_dashboard_enabled_summary_on">開始紀錄的應用程式也可以存取紀錄的資料。</string>
<string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string>
<!-- Settings import/export -->
<string name="settings_import_section">匯入</string>
<string name="settings_export_section">匯出</string>
<string name="settings_prevent_reimport_tracks_title">防止重新匯入蹤跡</string>
<!-- Share Track -->
<string name="share_track_share_file_body">你應該會喜歡這個蹤跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看蹤跡。\n\n%1$s</string>
<string name="share_track_subject">我想跟你分享蹤跡資訊。</string>
<!-- Stats -->
<string name="stats_average_moving_pace">平均移動步速</string>
<string name="stats_average_moving_speed">平均移動速度</string>
<string name="stats_average_pace">平均速度</string>
<string name="stats_average_pace">平均步速</string>
<string name="stats_average_speed">平均速度</string>
<string name="stats_altitude">海拔高度</string>
<string name="stats_fastest_pace">最快速度</string>
<string name="stats_fastest_pace">最高步速</string>
<string name="stats_gain">增加</string>
<string name="stats_loss">減少</string>
<string name="stats_coordinates">緯度/經度</string>
<string name="stats_latitude">緯度</string>
<string name="stats_longitude">經度</string>
<string name="stats_max_speed">速限</string>
<string name="stats_max_speed">最高速度</string>
<string name="stats_moving_time">移動時間</string>
<string name="stats_pace">速度</string>
<string name="stats_pace">步速</string>
<string name="stats_speed">速度</string>
<string name="stats_distance">距離</string>
<string name="stats_total_time">總時間</string>
<string name="track_name_format">%1$d 號足跡</string>
<string name="stats_split_interval">分段間隔</string>
<string name="stats_sensors_heart_rate">心率</string>
<string name="stats_sensors_cadence">踏頻</string>
<string name="stats_sensors_power">功率</string>
<string name="stats_clock">時鐘</string>
<!-- Track -->
<string name="track_name_format">%1$d 號蹤跡</string>
<string name="track_recording_notification_accuracy">定位精度:%1$s</string>
<string name="track_distance_notification">距離:%1$s</string>
<string name="track_speed_notification">速度:%1$s</string>
<string name="track_pace_notification">步速:%1$s</string>
<string name="track_markers">「%1$s」標記</string>
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個足跡與其中的標記。</string>
<string name="track_delete_one_confirm_title">確定要刪除足跡?</string>
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的足跡與其中的標記。</string>
<!-- Track Delete -->
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個蹤跡與其中的標記。</string>
<string name="track_delete_one_confirm_title">確定要刪除蹤跡?</string>
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的蹤跡與其中的標記。</string>
<string name="track_delete_progress_message">刪除中…</string>
<string name="track_delete_number_of_tracks">%1$d 蹤跡</string>
<string name="track_delete_not_recording">此蹤跡無法被刪除,因為它目前正在錄製。</string>
<string name="track_discarding">廢棄蹤跡</string>
<!-- Track Detail -->
<string name="track_detail_stats_tab">統計資料</string>
<string name="track_detail_intervals_tab">間隔</string>
<!-- Track Edit -->
<string name="track_edit_activity_type">活動類型 (例如健行)</string>
<string name="track_edit_activity_type_hint">活動類型</string>
<!-- Track List -->
<string name="track_list_empty_message">按這裡開始記錄新的冒險</string>
<!-- Aggregated Statistics -->
<string name="aggregated_stats_empty_message">紀錄您的第一個蹤跡以檢視綜合統計資料</string>
<string name="aggregated_stats_filter_no_results">沒有指定篩選的活動</string>
<!-- Interval List -->
<string name="interval_list_empty_message">還有有任何間格</string>
<!-- Unit -->
<string name="unit_feet">英尺</string>
<string name="unit_kilometer">公里</string>
<string name="unit_kilometer_per_hour">公里/小時</string>
<string name="unit_meter">公尺</string>
<string name="unit_mile">英里</string>
<string name="unit_mile_per_hour">英里/小時</string>
<string name="unit_nautical_mile">海里</string>
<string name="unit_knots">節</string>
<string name="unit_minute_per_kilometer">分鐘/公里</string>
<string name="unit_minute_per_mile">分鐘/英里</string>
<string name="unit_minute_per_nautical_mile">分鐘/海里</string>
<!-- Location Coordinates -->
<string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<!-- Value -->
<string name="value_integer_feet_excellent_gps">%1$d 英尺 (GPS 訊號佳)</string>
<string name="value_integer_feet_recommended">%1$d 英尺 (建議值)</string>
<string name="value_integer_kilometer_hour">%1$d 公里/小時</string>
<string name="value_float_kilometer_hour">%1$.1f 公里/小時</string>
<string name="value_float_kilometer_hour_recommended">%1$.1f 公里/小時 (建議值)</string>
<string name="value_integer_meter_excellent_gps">%1$d 公尺 (GPS 訊號佳)</string>
<string name="value_integer_meter_poor_gps">%1$d 公尺 (GPS 訊號差)</string>
<string name="value_integer_meter_recommended">%1$d 公尺 (建議值)</string>
<string name="value_integer_mile_hour">%1$d 英里/小時</string>
<string name="value_integer_knots">%1$d 節</string>
<string name="value_float_mile_hour">%1$.1f 英里/小時</string>
<string name="value_float_knots">%1$.1f 節</string>
<string name="value_float_mile_hour_recommended">%1$.1f 英里/小時 (建議值)</string>
<string name="value_float_knots_recommended">%1$.1f 節 (建議值)</string>
<string name="value_integer_minute">%1$d 分鐘</string>
<string name="value_integer_second">%1$d 秒</string>
<string name="value_none">無</string>
<string name="value_off">關閉</string>
<string name="value_smallest_recommended">最小 (建議值)</string>
<!-- Voice -->
<plurals name="voiceHours">
<item quantity="one">1 小時</item>
<item quantity="other">%1$d 小時</item>
</plurals>
<plurals name="voiceMinutes">
<item quantity="one">1 分鐘</item>
<item quantity="other">%1$d 分鐘</item>
</plurals>
<plurals name="voiceSeconds">
<item quantity="one">1 秒</item>
<item quantity="other">%1$d 秒</item>
</plurals>
<plurals name="voiceSpeedKilometersPerHour">
<item quantity="other">每小時 %1$.1f 公里</item>
<item quantity="one">1 公里每小時</item>
<item quantity="other">%1$.1f 公里每小時</item>
</plurals>
<plurals name="voiceSpeedMilesPerHour">
<item quantity="other">每小時 %1$.1f 公里</item>
<item quantity="one">1 英里每小時</item>
<item quantity="other">%1$.1f 英里每小時</item>
</plurals>
<plurals name="voiceSpeedMKnots">
<item quantity="one">1 節</item>
<item quantity="other">%1$.1f 節</item>
</plurals>
<string name="voice_per_kilometer">每公里</string>
<string name="voice_per_mile">每英里</string>
<string name="voice_per_nautical_mile">每海里</string>
<string name="lap_time">單圈時間</string>
<string name="pace">步速</string>
<string name="lap_speed">單圈速度</string>
<string name="speed">平均移動速度</string>
<string name="total_distance">總距離</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 公里</item>
<item quantity="other">%1$.1f 公里</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 英里</item>
<item quantity="other">%1$.1f 英里</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">1 海里</item>
<item quantity="other">%1$.1f 海里</item>
</plurals>
<string name="average_heart_rate">平均心率</string>
<string name="current_heart_rate">目前心率</string>
<!-- Waypoint Type -->
<string name="waypoint_type_atm">提款機</string>
<string name="waypoint_type_bank">銀行</string>
<string name="waypoint_type_bar">酒吧</string>
@@ -277,4 +649,30 @@
<string name="waypoint_type_waterfall">瀑布</string>
<string name="waypoint_type_water_fountain">噴泉</string>
<string name="waypoint_type_waypoint">途經點</string>
<!-- Share image -->
<string name="share_image_subject">我想與你分享一張圖片</string>
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
<string name="hold_to_stop">按住以停止</string>
<string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string>
<string name="instant_export_enabled_title">即時運動後匯出</string>
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
<!-- Fields -->
<string name="field_set_primary">主要的</string>
<string name="field_set_secondary">次要的</string>
<string name="field_remove_from_layout">移除</string>
<string name="no_compatible_file_manager_installed">沒有安裝相容的檔案管理器</string>
<string name="error_crash_title">%1$s 已崩潰</string>
<string name="share_crash_report">分享</string>
<string name="report_bug">回報 bug</string>
<string name="select_show_on_map_behavior">選擇 \"在地圖上顯示\" 行為</string>
<string name="show_on_map_format_ask">詢問</string>
<string name="show_on_dashboard">OpenTracks 儀表板 API</string>
<string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
<string name="always">總是</string>
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string>
</resources>
+2
View File
@@ -235,6 +235,8 @@
<item>1.2</item>
<item>1.3</item>
</string-array>
<string name="voice_on_device_speaker_key" translatable="false">voiceOnDeviceSpeaker</string>
<bool name="voice_on_device_speaker_default" translatable="false">true</bool>
<string name="voice_announce_total_distance_key" translatable="false">voiceAnnounceTotalDistance</string>
<string name="voice_announce_moving_time_key" translatable="false">voiceAnnounceMovingTime</string>
<string name="voice_announce_average_speed_pace_key" translatable="false">voiceAnnounceAverageSpeedPace</string>
+1
View File
@@ -575,6 +575,7 @@ limitations under the License.
<item quantity="one">1 knot</item>
<item quantity="other">%1$.1f knots</item>
</plurals>
<string name="voice_on_device_speaker_title">Use the device\'s speaker</string>
<string name="voice_per_kilometer">per kilometer</string>
<string name="voice_per_mile">per mile</string>
<string name="voice_per_nautical_mile">per nautical mile</string>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<include domain="sharedpref" path="." requireFlags="clientSideEncryption"/>
<include domain="database" path="." requireFlags="clientSideEncryption"/>
<include domain="external" path="." requireFlags="clientSideEncryption"/>
</full-backup-content>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup disableIfNoEncryptionCapabilities="true">
<include domain="sharedpref" path="."/>
<include domain="database" path="."/>
<include domain="external" path="."/>
</cloud-backup>
<device-transfer>
<include domain="sharedpref" path="."/>
<include domain="database" path="."/>
<include domain="external" path="."/>
</device-transfer>
</data-extraction-rules>
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="ar"/>
<locale android:name="bg"/>
<locale android:name="ca"/>
<locale android:name="cs"/>
<locale android:name="da"/>
<locale android:name="de"/>
<locale android:name="el"/>
<locale android:name="en"/>
<locale android:name="es"/>
<locale android:name="et"/>
<locale android:name="eu"/>
<locale android:name="fa"/>
<locale android:name="fi"/>
<locale android:name="fr"/>
<locale android:name="ga"/>
<locale android:name="gl"/>
<locale android:name="hi"/>
<locale android:name="hr"/>
<locale android:name="hu"/>
<locale android:name="in"/>
<locale android:name="it"/>
<locale android:name="iw"/>
<locale android:name="ja"/>
<locale android:name="ko"/>
<locale android:name="lt"/>
<locale android:name="lv"/>
<locale android:name="mo"/>
<locale android:name="ms"/>
<locale android:name="nb"/>
<locale android:name="nl"/>
<locale android:name="pl"/>
<locale android:name="pt"/>
<locale android:name="ro"/>
<locale android:name="ru"/>
<locale android:name="sk"/>
<locale android:name="sl"/>
<locale android:name="sr"/>
<locale android:name="sv"/>
<locale android:name="th"/>
<locale android:name="tl"/>
<locale android:name="tr"/>
<locale android:name="uk"/>
<locale android:name="vi"/>
<locale android:name="zh"/>
</locale-config>
@@ -26,6 +26,10 @@
android:title="@string/menu_voice_rate"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:defaultValue="@bool/voice_on_device_speaker_default"
android:key="@string/voice_on_device_speaker_key"
android:title="@string/voice_on_device_speaker_title" />
<PreferenceCategory android:title="@string/settings_announcements_title">
<SwitchPreference