Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9b8e46b32 | |||
| c79fc2f353 | |||
| e283e993bb | |||
| 3f1e74e71e | |||
| 300453e3e6 | |||
| ac8eea377e | |||
| cc44e8e9bf | |||
| 3860472e6b | |||
| 69029486b6 | |||
| b407f86d5e | |||
| 731adc7e31 | |||
| 858149e718 | |||
| 34e796f73c | |||
| 3ac71361b8 | |||
| e409b315b4 | |||
| 24eb768707 | |||
| 17ad13d559 | |||
| 3b71212905 | |||
| 16a29c5e31 | |||
| e421164282 | |||
| de01157964 | |||
| 8ef51ec318 | |||
| d2139d70ad | |||
| d1a37366be | |||
| 52d7c8a6cb | |||
| c62e56e5a3 | |||
| 6b89092adf | |||
| 118a56b8d6 | |||
| 7fb993014c | |||
| 09f72af66c | |||
| 815f111ca6 | |||
| 40d2bf0c22 | |||
| a77b7a0626 | |||
| 1a00b4b779 | |||
| 6c5c47240c | |||
| 3c32da970b | |||
| 9cff62c5ba | |||
| 424f93b4da | |||
| 3a23a412c8 | |||
| 9d64989ef1 | |||
| da62d26409 | |||
| a350077e5c | |||
| 36b6cd3c98 | |||
| 203b457ad0 | |||
| 0873b3bd12 | |||
| a7f7e3fca1 | |||
| a52beffef5 | |||
| e6f6d61b84 | |||
| 4a167c9335 | |||
| 16857a9968 | |||
| 094a20c379 | |||
| 0d3cf4ce0d | |||
| 8a2d1c42a8 | |||
| a5b44175a3 | |||
| db5c9479df | |||
| 16f238f34b | |||
| a57b5cd6ee | |||
| 5e54cb8fda | |||
| 0934e7f10c | |||
| 6741ef4d9f | |||
| f2ac1edc4b | |||
| b44bab1ab5 | |||
| 574bd38a35 | |||
| 374959f340 | |||
| 3815ee6c59 | |||
| 5e4924a5e1 | |||
| 8123104f39 | |||
| a9e0c4916d | |||
| 58803c975a | |||
| d7dc59e547 | |||
| 5cf8b67954 | |||
| fb70486bdc | |||
| 18b21a6a3e | |||
| 71288cd9be | |||
| 220545da9f | |||
| 43e9c42915 | |||
| 28944eff07 | |||
| 92f9f94423 | |||
| 8ae268fab2 | |||
| 346264025b | |||
| 0a810e7123 | |||
| 02f3fc1940 | |||
| dd1c5667a0 | |||
| 0f489f1cc9 | |||
| a07f11589b | |||
| 209a320433 | |||
| 8ab9ed0e31 | |||
| 80ca4e647a | |||
| a8de9cb216 | |||
| 12c21b7940 | |||
| 4c5ff546a4 | |||
| 2e0835b2ec | |||
| 18ea8fea87 | |||
| 02a69c8817 | |||
| cab70d665c | |||
| c42f833703 | |||
| 39838232b5 | |||
| 2b346b3f18 | |||
| 7c5115fc56 | |||
| cb71c40350 | |||
| 22bde4af3c |
@@ -9,7 +9,7 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Archive
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: OpenTracks-nightly.apk
|
||||
path: ./build/outputs/apk/nightly/*.apk
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
|
||||
@@ -10,20 +10,20 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [ 23, 30 ]
|
||||
api-level: [ 24, 30 ]
|
||||
target: [ default ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2.23.0
|
||||
uses: reactivecircus/android-emulator-runner@v2.24.0
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Archive
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: build/reports/androidTests/
|
||||
|
||||
@@ -60,7 +60,6 @@ captures/
|
||||
.idea/encodings.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/libraries/
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/shelf/
|
||||
|
||||
@@ -130,6 +130,7 @@ The API can be invoked by sending an explicit Intent to start an activity.
|
||||
|
||||
`Class`:
|
||||
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
|
||||
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
|
||||
|
||||
For testing via adb: `adb shell am start -n "package/class"`
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ We do not support type of movement (i.e., walking vs running).
|
||||
|
||||
* Polar Stride
|
||||
* Stryd (incl. power)
|
||||
* Wahoo Tickr X (Cadence appears to be reported in SPM rather than RPM)
|
||||
|
||||
## Tested Barometric Sensor Smartphones
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ set -e
|
||||
# Folder where svg screenshots are expected.
|
||||
SRC_DIR="screenshots-svg/"
|
||||
# Folder where png files will be saved.
|
||||
DST_DIR="fastlane/metadata/android/en-US/phoneScreenshots/"
|
||||
DST_DIR="fastlane/metadata/android/en-US/images/phoneScreenshots/"
|
||||
|
||||
echo "Exporting svg files to png..."
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileSdkVersion 31
|
||||
compileSdkVersion 32
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
@@ -59,13 +59,13 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4773
|
||||
versionName "v3.26.0"
|
||||
versionCode 4874
|
||||
versionName "v4.0.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
targetSdkVersion 32
|
||||
|
||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
@@ -122,15 +122,15 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
|
||||
implementation 'com.google.android.material:material:1.6.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.5.1'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||
}
|
||||
|
||||
@@ -29,4 +29,26 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="accuracy_horizontal"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
Horizontal accuracy in meters of the current TrackPoint; 68% chance that the actual
|
||||
location is
|
||||
within this radius around the measured location.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="accuracy_vertical"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
Vertical accuracy in meters of the current TrackPoint; 68% chance that the actual
|
||||
location is
|
||||
within this radius around the measured location.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
@@ -1,37 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
enable-background="new 0 0 95.334 100"
|
||||
height="100"
|
||||
id="Ebene_1"
|
||||
version="1.1"
|
||||
viewBox="0 0 100 100"
|
||||
width="100"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px"
|
||||
xml:space="preserve"><metadata
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
enable-background="new 0 0 95.334 100"
|
||||
height="100"
|
||||
id="Ebene_1"
|
||||
version="1.1"
|
||||
viewBox="0 0 100 100"
|
||||
width="100"
|
||||
x="0px"
|
||||
y="0px"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="LOGO.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.94"
|
||||
inkscape:cx="50"
|
||||
inkscape:cy="50"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Ebene_1" /><metadata
|
||||
id="metadata16"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata>
|
||||
<defs id="defs14" />
|
||||
<g id="g9"
|
||||
transform="translate(-1.0593197,1.9067775)">
|
||||
<linearGradient gradientUnits="userSpaceOnUse"
|
||||
id="SVGID_1_"
|
||||
x1="28.7554"
|
||||
x2="82.789497"
|
||||
y1="81.535202"
|
||||
y2="9.2979002">
|
||||
<stop style="stop-color:#FBB22F"
|
||||
id="stop2"
|
||||
offset="0" />
|
||||
<stop style="stop-color:#F58820"
|
||||
id="stop4"
|
||||
offset="1" />
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata>
|
||||
<defs
|
||||
id="defs14"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient862"><stop
|
||||
style="stop-color:#ff6d00;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop858" /><stop
|
||||
style="stop-color:#ff6d00;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop860" /></linearGradient><linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="SVGID_1_"
|
||||
x1="28.7554"
|
||||
x2="82.789497"
|
||||
y1="81.535202"
|
||||
y2="9.2979002"
|
||||
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||
<stop
|
||||
style="stop-color:#FBB22F;fill-opacity:1;fill:url(#linearGradient849)"
|
||||
id="stop853"
|
||||
offset="0" />
|
||||
<stop
|
||||
style="stop-color:#F58820;fill-opacity:1;fill:url(#linearGradient849)"
|
||||
id="stop855"
|
||||
offset="1" />
|
||||
</linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient847"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop843" /><stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop845" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient847"
|
||||
id="linearGradient849"
|
||||
x1="83.161415"
|
||||
y1="8.9067831"
|
||||
x2="10.194695"
|
||||
y2="92.068085"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient862"
|
||||
id="linearGradient866"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="62.43222"
|
||||
y1="35.349697"
|
||||
x2="9.7942638"
|
||||
y2="93.02298" /></defs>
|
||||
<g
|
||||
id="g9"
|
||||
transform="translate(-1.0593197,1.9067775)"
|
||||
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="SVGID_1_-3"
|
||||
x1="28.7554"
|
||||
x2="82.789497"
|
||||
y1="81.535202"
|
||||
y2="9.2979002"
|
||||
style="fill-opacity:1;fill:url(#linearGradient849)">
|
||||
<stop
|
||||
style="stop-color:#FBB22F;fill-opacity:1;fill:url(#linearGradient849)"
|
||||
id="stop2"
|
||||
offset="0" />
|
||||
<stop
|
||||
style="stop-color:#F58820;fill-opacity:1;fill:url(#linearGradient849)"
|
||||
id="stop4"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_1_)"
|
||||
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
||||
id="path7" />
|
||||
<path
|
||||
style="fill:url(#linearGradient866);fill-opacity:1"
|
||||
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
||||
id="path7" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
enable-background="new 0 0 95.334 100"
|
||||
height="100"
|
||||
id="Ebene_1"
|
||||
version="1.1"
|
||||
viewBox="0 0 100 100"
|
||||
width="100"
|
||||
x="0px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0px"
|
||||
xml:space="preserve"><metadata
|
||||
id="metadata16"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata>
|
||||
<defs id="defs14" />
|
||||
<g id="g9"
|
||||
transform="translate(-1.0593197,1.9067775)">
|
||||
<linearGradient gradientUnits="userSpaceOnUse"
|
||||
id="SVGID_1_"
|
||||
x1="28.7554"
|
||||
x2="82.789497"
|
||||
y1="81.535202"
|
||||
y2="9.2979002">
|
||||
<stop style="stop-color:#FBB22F"
|
||||
id="stop2"
|
||||
offset="0" />
|
||||
<stop style="stop-color:#F58820"
|
||||
id="stop4"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_1_)"
|
||||
d="M 83.548,10.927 C 84.255,8.511 83.102,7.649 80.985,9.01 L 11.146,53.921 c -2.117,1.361 -1.795,2.628 0.714,2.815 L 40.865,58.9 c 2.51,0.188 5.291,2.267 6.18,4.621 l 10.282,27.208 c 0.89,2.354 2.195,2.304 2.902,-0.111 z"
|
||||
id="path7" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="125"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
viewBox="0 0 125 125"
|
||||
width="125"
|
||||
sodipodi:docname="ic_button_done.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.2457711"
|
||||
inkscape:cx="104.26334"
|
||||
inkscape:cy="71.321036"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs6" />
|
||||
<circle
|
||||
style="fill:none;fill-opacity:1;stroke:#ff6d00;stroke-width:5.03185;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
cx="62.5"
|
||||
cy="62.5"
|
||||
id="path12"
|
||||
r="59.984074" />
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<circle
|
||||
style="fill:#ff6d00;fill-opacity:1;stroke:none;stroke-width:6.91728;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
cx="62.5"
|
||||
cy="62.5"
|
||||
id="path835"
|
||||
r="50" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:6.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 38.620483,63.865643 52.899055,78.144215 86.379516,44.663754"
|
||||
id="path1000" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="125"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
viewBox="0 0 125 125"
|
||||
width="125"
|
||||
sodipodi:docname="ic_button_resume.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
id="namedview9"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.176"
|
||||
inkscape:cx="85.245821"
|
||||
inkscape:cy="70.9649"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs6" />
|
||||
<circle
|
||||
style="fill:none;fill-opacity:1;stroke:#ff6d00;stroke-width:5.03185;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
cx="62.5"
|
||||
cy="62.5"
|
||||
id="path12"
|
||||
r="59.984074" />
|
||||
<circle
|
||||
style="fill:#ff6d00;fill-opacity:1;stroke:none;stroke-width:6.91728;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
cx="62.5"
|
||||
cy="62.5"
|
||||
id="path835"
|
||||
r="50" />
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:title></dc:title>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:6.67087;stroke-linecap:round;stroke-opacity:1"
|
||||
id="path837"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="61.453945"
|
||||
sodipodi:cy="62.5"
|
||||
sodipodi:r1="26.146271"
|
||||
sodipodi:r2="13.073136"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="M 87.600216,62.5 67.990513,73.821668 48.38081,85.143335 l -10e-7,-22.643336 10e-7,-22.643334 19.609704,11.321667 z"
|
||||
inkscape:transform-center-x="-6.5365678" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,4 @@
|
||||
v3.26.1: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Contextual action bar didn't overlap toolbar in night theme.
|
||||
@@ -0,0 +1,17 @@
|
||||
v3.27.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Voice annoucements: add 100m, 200m, and 500m
|
||||
- Interval: add 100m and 500m
|
||||
- Clock shows now also seconds
|
||||
- GPX, KML, KMZ: export and import horizontal accuracy
|
||||
- GPX: export cumulative distance via clustrust extension
|
||||
- CSV: basic export (TrackPoints only; not for backup)
|
||||
- Workaround for Tickr X as it reports SPM instead of RPM
|
||||
|
||||
Bugfixes:
|
||||
- Adhere to recording distance interval;
|
||||
TrackPoints were recorded although they were too close
|
||||
- Prevent crash on stop recording (race condition)
|
||||
- Prevent crash when using multiple sensors (race condition)
|
||||
- Prevent crash when opening OpenTracks via notification
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.28.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Configurable filename for export/sharing (default: {starttime}_{name}.{suffix})
|
||||
- Vertical GPS accuracy is now stored and can be exported/imported
|
||||
(API level >= 26).
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
v4.0.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- UI refactoring with a Bottom App Bar.
|
||||
- Public API: implemented STOP properly.
|
||||
- Cleanup: use explicit preference identifiers for TrackFileFormat.
|
||||
- Improvements on BLE handler.
|
||||
- Add exception handler and show error activity so users can send reports about bugs.
|
||||
- Android 13: launcher icon, provide a themed icon.
|
||||
- Change track title capitalization to sentences.
|
||||
- Translations updated.
|
||||
|
||||
Bugfix:
|
||||
- Sharing track added file suffix twice.
|
||||
- Unit system not loaded correctly.
|
||||
- File format template was not loaded (NPE).
|
||||
- Public API: respect post-workout export.
|
||||
|
||||
Developer:
|
||||
- Move to androix.constraintlayout.
|
||||
- UI tests slightly more robust.
|
||||
- Refactor permission request.
|
||||
- Upgrade to gradlew 7.4.2
|
||||
- Bump material from 1.5.0 to 1.6.0
|
||||
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 560 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 812 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 630 KiB After Width: | Height: | Size: 695 KiB |
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 509 KiB |
|
Before Width: | Height: | Size: 798 KiB After Width: | Height: | Size: 872 KiB |
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Wijzigingen:
|
||||
- "Toon op kaart"-functionaliteit via KML in plaats van KMZ
|
||||
- Spraakaankondigingen bevatten geen app naam
|
||||
- Tijdens opname: hoogtewinst/-verlies wordt elke seconde bijgewerkt
|
||||
- Winkel beschrijvingen vertaald
|
||||
|
||||
Bufixes:
|
||||
- Ongeldige vertalingen van stemboodschappen opgelost
|
||||
|
||||
Ontwikkelaar:
|
||||
- Android12-toestemmingen (BLUETOOTH_SCAN)
|
||||
- Android12-toestemmingen (ACCESS_COARSE_LOCATION)
|
||||
- Assertiviteit voor TrackPoints (testen)
|
||||
- Ongebruikte vertalingen verwijderd
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.19.0: OpenTracks
|
||||
|
||||
Wijzigingen:
|
||||
- Ondersteuning voor Running Speed en Cadence (RCS) sensoren
|
||||
- Bij het delen van een track wordt een afbeelding gemaakt
|
||||
- Niet trillen bij starten van opnemen
|
||||
- Bij opnemen: UI toont recentst ontvangen hoogte, snelheid en richting (ook al is deze niet opgeslagen)
|
||||
|
||||
Bugfix:
|
||||
- Berekening van totale tijd mag geen pauze-tijd bevatten (vereist opnieuw importeren)
|
||||
- (opnieuw) Gebruik aangepaste overzichten indien aanwezig
|
||||
|
||||
Ontwikkelaar:
|
||||
- Upgrade naar SDK31 en AGP7.0.0 (vereist JDK11)
|
||||
- TrackRecordingService: sensorfusie (TrackPointCreator) en TrackPoint-opslag (TrackRecordingManager) geëxtraheerd
|
||||
- ExportImportTest is nu deterministisch/constant
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.25.0: OpenTracks
|
||||
|
||||
Veranderingen:
|
||||
- GPS is nu optioneel incl. waarschuwing GPS uitgeschakeld
|
||||
- PublicAPI: andere apps kunnen nu opgenomen gegevens ontvangen als ze een opname starten (geïmplementeerd voor Gadgetbridge)
|
||||
- Nieuwe activiteiten: zwemmen, buiten zwemmen, en training
|
||||
- Verwijderd: share pic functie
|
||||
|
||||
Bugfix:
|
||||
- UI: snelheid/hoogte met wisselingen tussen 0,0 en de werkelijke waarde
|
||||
- Tracks met emoji's kunnen worden geëxporteerd
|
||||
@@ -0,0 +1,5 @@
|
||||
v3.25.1: OpenTracks
|
||||
|
||||
Verbetering:
|
||||
- Automatische export: hopelijk voorkomen van stille mislukkingen
|
||||
- Automatische export: voorkom crash
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.26.0: OpenTracks
|
||||
|
||||
Wijzigingen:
|
||||
- Gesproken meldingen zijn instelbaar
|
||||
- Gesproken meldingen van hartslag
|
||||
|
||||
Bugfix:
|
||||
- Crash due to race condition
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.26.1: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Contextuele actie balk overlapte de werkbalk niet in het nachtthema.
|
||||
@@ -1,11 +1,22 @@
|
||||
OpenTracks is een sport tracking applicatie die uw privacy volledig respecteert.
|
||||
Een sport tracker maatje met respect voor privacy.
|
||||
|
||||
Kenmerken:
|
||||
* Sport tracking: volg uw sport en outdoor activiteiten
|
||||
* Foto's en markeringen: markeer interessante locaties tijdens het volgen
|
||||
* Hoogte winst en verlies: via barometrische sensor
|
||||
* Hoogte weergegeven in EGM2008 (boven gemiddeld zeeniveau); geëxporteerd als WGS84
|
||||
* Spraakaankondigingen
|
||||
* Ondersteunt Bluetooth LE-sensoren: hartslag, snelheid en afstand (fietsen), cadans (fietsen), en vermogensmeter (fietsen)
|
||||
* Exporteer gegevens: exporteer tracks als KMZ (incl. foto's), KML, of GPX
|
||||
* Geen toegang tot internet: Internet wordt niet gebruikt
|
||||
Sport en outdoor activiteiten geven geluk, vaardigheid en zelfvertrouwen.
|
||||
|
||||
Geef je gezondheid waarde door de conditie bij te houden.
|
||||
Registreert terwijl je hardloopt of wandelt, en geeft je een fietscomputer met een groter scherm voor op de fiets.
|
||||
Markeer interessante locaties langs de weg met foto's.
|
||||
Bewaar geregistreerde statistieken tot in detail voor analyse.
|
||||
Deel alleen de gegevens die je aan anderen wilt laten zien.
|
||||
|
||||
* Spraakaankondigingen.
|
||||
* Ondersteunt Bluetooth LE sensoren: hartslag, snelheid en afstand (fietsen), cadans (fietsen), en vermogensmeter (fietsen).
|
||||
* Hoogtewinst en -verlies: via barometrische sensor.
|
||||
* Hoogte weergegeven in EGM2008 (boven gemiddeld zeeniveau); geëxporteerd als WGS84.
|
||||
* Gegevens exporteren als tracks, hetzij als KMZ (incl. foto's), KML, of GPX.
|
||||
* Geen Internet toegang of extra toegangsrechten.
|
||||
* Donker en licht thema, met inachtneming van de systeeminstellingen.
|
||||
* Geen advertenties.
|
||||
|
||||
Libre software / Vrije Software / Open Source
|
||||
wat betekent dat je de broncode mag gebruiken, bestuderen, veranderen en delen.
|
||||
Gelicenseerd Apache 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
Een sport tracker die uw privacy volledig respecteert.
|
||||
Een sport tracker maatje met respect voor privacy.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.27.0: OpenTracks
|
||||
|
||||
Mudanças:
|
||||
- Anúncios de voz: adicionado 100m, 200m e 500m
|
||||
- Intervalo: adicionado 100m e 500m
|
||||
- O relógio mostra agora também os segundos
|
||||
- GPX, KML, KMZ: uma maior precisão horizontal de exportação e importação
|
||||
- GPX: exporta a distância acumulada via extensão clustrust
|
||||
- CSV: exportação básica (apenas TrackPoints; não para backup)
|
||||
- Solução alternativa para o Tickr X, pois relata SPM em vez de RPM
|
||||
|
||||
Correções de bugs:
|
||||
- Foi Aderido ao intervalo de distância de gravação; Os TrackPoints foram registrados embora estivessem muito próximos
|
||||
- Prevenir travamento ao parar de gravar (condição de corrida)
|
||||
- Evite acidentes ao usar vários sensores (condição de corrida)
|
||||
- Evite travamentos ao abrir o OpenTracks via notificação
|
||||
@@ -1 +1 @@
|
||||
Um rastreador esportivo que respeita totalmente a sua privacidade.
|
||||
Um rastreador esportivo que respeita a sua privacidade.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.26.0: OpenTracks
|
||||
|
||||
Изменения:
|
||||
- Голосовые оповещения теперь настраиваются
|
||||
- Голосовые оповещения о частоте сердечных сокращений
|
||||
|
||||
Исправление:
|
||||
- Вылет из-за состояния гонки
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.26.1: OpenTracks
|
||||
|
||||
Исправление:
|
||||
- Контекстная панель действий не перекрывала панель инструментов в ночной теме.
|
||||
@@ -1,11 +1,22 @@
|
||||
OpenTracks - это приложение для спортивного слежения, которое полностью уважает вашу конфиденциальность.
|
||||
Спортивный трекер уважающий вашу приватность.
|
||||
|
||||
Особенности:
|
||||
* Спортивный трекинг: отслеживайте свои занятия спортом и активный отдых.
|
||||
* Фотографии и маркеры: отмечайте интересные места во время отслеживания.
|
||||
* Набор и потеря высоты: через барометрический датчик.
|
||||
* Высота отображается в EGM2008 (над средним уровнем моря); экспортируется в WGS84.
|
||||
* Голосовые уведомления
|
||||
* Поддержка датчиков Bluetooth LE: пульс, скорость и расстояние (велоспорт), каденция (велоспорт) и измеритель мощности (велоспорт)
|
||||
* Экспорт данных: экспорт треков в формате KMZ (включая фотографии), KML или GPX.
|
||||
* Нет доступа к Интернету: Интернет не используется
|
||||
Спорт и иные активности дарят радость, мастерство и уверенность в себе.
|
||||
|
||||
Цените свое здоровье, следя за тренировками.
|
||||
Записывает, как вы бегаете или идете, и предоставляет вам функционал велокомпьютера с большим экраном для езды на велосипеде.
|
||||
Отмечайте интересные места на своем пути с помощью фотографий.
|
||||
Храните записанные данные в мельчайших подробностях для анализа.
|
||||
Делитесь только теми данными, которые хотите, чтобы другие имели доступ к ним.
|
||||
|
||||
* Голосовые уведомления.
|
||||
* Поддержка датчиков Bluetooth LE: пульс, скорость и дистанция (для велосипеда), частота вращения педалей (для велосипеда) и измеритель мощности (для велосипеда).
|
||||
* Набор и потеря высоты: с помощью барометрического датчика.
|
||||
* Высота указана в EGM2008 (над средним уровнем моря); экспортируется как WGS84.
|
||||
* Экспорт данных в виде треков формата KMZ (включая фотографии), KML или GPX.
|
||||
* Не требует соединения с Интернетом и лишних разрешений.
|
||||
* Темная и светлая темы с учетом системных настроек.
|
||||
* Без рекламы.
|
||||
|
||||
Libre software / Свободное программное обеспечение / Открытый исходный код
|
||||
означает, что вы можете использовать, изучать, изменять и делиться исходным кодом.
|
||||
Лицензия Apache 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
Спортивный трекер, который полностью соблюдает вашу конфиденциальность.
|
||||
Спортивный трекер, уважающий вашу конфиденциальность.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Gizliliğinize saygı duyan bir spor takipçisi.
|
||||
|
||||
Spor ve açık hava etkinlikleri mutluluk, beceri ve güven verir.
|
||||
|
||||
Antrenmanınızı takip ederek sağlığınıza değer verin.
|
||||
Koşarken veya yürürken kaydeder ve size bisiklet sürmek için daha büyük ekranlı bir bisiklet bilgisayarı verir.
|
||||
Yol boyunca ilginç yerleri resimlerle işaretleyin.
|
||||
Analiz için kaydedilen istatistikleri çok ayrıntılı tutun.
|
||||
Yalnızca başkalarının sahip olmasını istediğiniz verileri paylaşın.
|
||||
|
||||
* Sesli duyurular.
|
||||
* Bluetooth LE sensörlerini destekler: kalp atış hızı, hız ve mesafe (bisiklet), tempo (bisiklet) ve güç ölçer (bisiklet).
|
||||
* İrtifa kazancı ve kaybı: barometrik sensör aracılığıyla.
|
||||
* EGM2008'de gösterilen rakım (ortalama deniz seviyesinin üzerinde); WGS84 olarak dışa aktarılır.
|
||||
* Verileri KMZ (fotoğraflar dahil), KML veya GPX olarak parça olarak dışa aktarın.
|
||||
* İnternet erişimi veya ekstra izin yok.
|
||||
* Sistem ayarlarına uygun olarak koyu ve açık tema.
|
||||
* Reklamsız.
|
||||
|
||||
Libre yazılımı / Özgür Yazılım / Açık Kaynak
|
||||
yani kaynak kodunu kullanabilir, inceleyebilir, değiştirebilir ve paylaşabilirsiniz.
|
||||
Lisanslı Apache 2.0
|
||||
@@ -0,0 +1 @@
|
||||
Gizliliğinize saygı duyan bir spor takipçisi.
|
||||
@@ -1,2 +1 @@
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
@@ -1,6 +1,5 @@
|
||||
#Wed Jul 28 23:10:13 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,101 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -106,80 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -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 init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -54,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -64,21 +64,6 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 862 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 588 KiB |
|
Before Width: | Height: | Size: 1001 KiB After Width: | Height: | Size: 1012 KiB |
@@ -78,17 +78,16 @@ public class EspressoDeleteTrackTest {
|
||||
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(5000));
|
||||
trackControllerStopButton.perform(waitFor(5000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
@@ -108,7 +107,7 @@ public class EspressoDeleteTrackTest {
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.toolbar),
|
||||
withId(R.id.bottom_app_bar),
|
||||
2),
|
||||
2),
|
||||
isDisplayed()));
|
||||
|
||||
@@ -5,18 +5,14 @@ import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
@@ -25,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -50,16 +45,15 @@ public class EspressoEditTrackRecordingTest {
|
||||
public void espressoEditTrackRecordingTest() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
ViewInteraction fabRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
fabRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(15000));
|
||||
trackControllerStopButton.perform(waitFor(4000));
|
||||
|
||||
// open menu
|
||||
openContextualActionModeOverflowMenu();
|
||||
@@ -68,58 +62,24 @@ public class EspressoEditTrackRecordingTest {
|
||||
onView(withText(R.string.menu_edit)).perform(click());
|
||||
|
||||
// change name for "New Name"
|
||||
ViewInteraction textInputEditText = onView(
|
||||
allOf(withId(R.id.track_edit_name),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1)));
|
||||
ViewInteraction textInputEditText = onView(withId(R.id.track_edit_name));
|
||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
||||
|
||||
ViewInteraction textInputEditText2 = onView(
|
||||
allOf(withId(R.id.track_edit_name), withText("New Name"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1),
|
||||
isDisplayed()));
|
||||
ViewInteraction textInputEditText2 = onView(allOf(withId(R.id.track_edit_name), withText("New Name"), isDisplayed()));
|
||||
textInputEditText2.perform(closeSoftKeyboard());
|
||||
|
||||
// save edition
|
||||
ViewInteraction appCompatButton = onView(
|
||||
allOf(withId(R.id.track_edit_save),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
3),
|
||||
1),
|
||||
isDisplayed()));
|
||||
ViewInteraction appCompatButton = onView(withId(R.id.track_edit_save));
|
||||
appCompatButton.perform(click());
|
||||
|
||||
//pause
|
||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
||||
|
||||
// wait; stay paused
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// resume
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
trackControllerStopButton.perform(longClick());
|
||||
|
||||
// check track's name is "New Name"
|
||||
ViewInteraction textView = onView(
|
||||
allOf(withId(R.id.stats_name_value),
|
||||
withParent(withParent(IsInstanceOf.instanceOf(android.widget.ScrollView.class))),
|
||||
isDisplayed()));
|
||||
textView.check(matches(withText("New Name")));
|
||||
// it's on track stopped activity and there are two buttons
|
||||
ViewInteraction resumeButton = onView(withId(R.id.resume_button));
|
||||
resumeButton.check(matches(isDisplayed()));
|
||||
ViewInteraction finishButton = onView(withId(R.id.finish_button));
|
||||
finishButton.check(matches(isDisplayed()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks;
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
@@ -39,34 +40,30 @@ public class EspressoUITest {
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_pause_resume_stop() {
|
||||
public void record_stop_resume_stop_finish() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
// TrackRecordingActivity: wait to record some time and then stop
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(5000))
|
||||
.perform(longClick());
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(5000));
|
||||
// TrackStoppedActivity: resume
|
||||
onView(withId(R.id.resume_button))
|
||||
.perform(click());
|
||||
|
||||
//pause
|
||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
||||
// TrackRecordingActivity: wait and then stop
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(5000))
|
||||
.perform(longClick());
|
||||
|
||||
// wait; stay paused
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// resume
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
// TrackStoppedActivity
|
||||
onView(withId(R.id.finish_button))
|
||||
.perform(click());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,13 +72,13 @@ public class EspressoUITest {
|
||||
public void record_move_through_tabs() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(1));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
@@ -5,12 +5,13 @@ import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
public class PreferenceMetricUnitRule implements TestRule {
|
||||
private final boolean metricUnits;
|
||||
public class PreferenceUnitSystemRule implements TestRule {
|
||||
private final UnitSystem unit;
|
||||
|
||||
public PreferenceMetricUnitRule(boolean metricUnits) {
|
||||
this.metricUnits = metricUnits;
|
||||
public PreferenceUnitSystemRule(UnitSystem unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -18,13 +19,13 @@ public class PreferenceMetricUnitRule implements TestRule {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits();
|
||||
final UnitSystem previousUnitSystem = PreferencesUtils.getUnitSystem();
|
||||
|
||||
try {
|
||||
PreferencesUtils.setMetricUnits(metricUnits);
|
||||
PreferencesUtils.setUnit(unit);
|
||||
base.evaluate();
|
||||
} finally {
|
||||
PreferencesUtils.setMetricUnits(previousMetricUnits);
|
||||
PreferencesUtils.setUnit(previousUnitSystem);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class TimezoneRule implements TestRule {
|
||||
|
||||
private final TimeZone mTimeZone;
|
||||
private TimeZone mDeviceTimeZone;
|
||||
|
||||
|
||||
public TimezoneRule(@NonNull TimeZone timeZone) {
|
||||
mTimeZone = timeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
try {
|
||||
mDeviceTimeZone = TimeZone.getDefault();
|
||||
TimeZone.setDefault(mTimeZone);
|
||||
base.evaluate();
|
||||
} finally {
|
||||
TimeZone.setDefault(mDeviceTimeZone);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -26,7 +27,7 @@ public class ChartPointTest {
|
||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, false);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, UnitSystem.IMPERIAL);
|
||||
|
||||
// then
|
||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||
@@ -39,7 +40,7 @@ public class ChartPointTest {
|
||||
statistics.setTotalDistance(Distance.of(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, true);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(1, (long) point.getTimeOrDistance());
|
||||
@@ -51,7 +52,7 @@ public class ChartPointTest {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(50, point.getAltitude(), 0.01);
|
||||
@@ -65,7 +66,7 @@ public class ChartPointTest {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertNull(point.getHeartRate());
|
||||
@@ -84,7 +85,7 @@ public class ChartPointTest {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ShareContentProviderTest {
|
||||
trackIds.add(new Track.Id(3));
|
||||
trackIds.add(new Track.Id(5));
|
||||
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "filename.suffix", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
|
||||
assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
|
||||
}
|
||||
@@ -40,6 +40,6 @@ public class ShareContentProviderTest {
|
||||
trackIds.add(new Track.Id(1));
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
|
||||
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_with_trackdetail_and_sensordata/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
|
||||
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/KML_WITH_TRACKDETAIL_AND_SENSORDATA/1/..%2F..%2F%261%3D1"), shareURIandMIME.first);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DistanceFormatterTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void testFormatDistance_metric() {
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.METRIC)
|
||||
.build(context);
|
||||
|
||||
// A large number in metric
|
||||
assertEquals("5.00 km", formatter.formatDistance(Distance.of(5000)));
|
||||
// A small number in metric
|
||||
assertEquals("100.00 m", formatter.formatDistance(Distance.of(100)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDistance_imperial() {
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.build(context);
|
||||
|
||||
// A large number in imperial
|
||||
assertEquals("3.11 mi", formatter.formatDistance(Distance.of(5000)));
|
||||
// A small number in imperial
|
||||
assertEquals("328.08 ft", formatter.formatDistance(Distance.of(100)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SpeedFormatterTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_pace_metric() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.METRIC)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
assertEquals("4:59", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
assertEquals("5:00", formatter.getSpeedParts(Speed.of(3.33)).first);
|
||||
|
||||
assertEquals("min/km", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_pace_imperial() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
// assertEquals("TODO", formatter.getSpeedParts(Speed.of(3.33)).first);
|
||||
|
||||
assertEquals("min/mi", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_speed_metric() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.METRIC)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(context);
|
||||
|
||||
assertEquals("11.9", formatter.getSpeedParts(Speed.of(3.31)).first);
|
||||
assertEquals("km/h", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts_speed_imperial() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.IMPERIAL)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(context);
|
||||
|
||||
assertEquals("7.5", formatter.getSpeedParts(Speed.of(3.34)).first);
|
||||
assertEquals("mph", formatter.getSpeedParts(Speed.zero()).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSpeed() {
|
||||
SpeedFormatter formatter = SpeedFormatter.Builder()
|
||||
.setDecimalCount(2)
|
||||
.setUnit(UnitSystem.METRIC)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(context);
|
||||
|
||||
assertEquals("4:59 min/km", formatter.formatSpeed(Speed.of(3.34)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.dennisguse.opentracks.io.file;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TimezoneRule;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class TrackFilenameGeneratorTest {
|
||||
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<String[]> data() {
|
||||
return Arrays.asList(new String[][]{
|
||||
{"{uuid}_{name}", "0000fee0_Best Track.gpx"},
|
||||
{"{name}_{uuid}", "Best Track_0000fee0.gpx"},
|
||||
{"{time}_{name}", "02_02_02_Best Track.gpx"},
|
||||
{"{date}_{name}", "2020-02-02_Best Track.gpx"},
|
||||
{ApplicationProvider.getApplicationContext().getString(R.string.export_filename_format_default), "2020-02-02_02_02_02_Best Track.gpx"},
|
||||
});
|
||||
}
|
||||
|
||||
private final TrackFilenameGenerator subject;
|
||||
private final String expected;
|
||||
|
||||
public TrackFilenameGeneratorTest(String template, String expected) {
|
||||
this.subject = new TrackFilenameGenerator(template);
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilenameTemplate() {
|
||||
// given
|
||||
Track track = new Track();
|
||||
track.setName("Best Track");
|
||||
track.setUuid(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
|
||||
track.getTrackStatistics().setStartTime(Instant.parse("2020-02-02T02:02:02Z"));
|
||||
|
||||
// when
|
||||
String filename = subject.format(track, TrackFileFormat.GPX);
|
||||
|
||||
// then
|
||||
assertEquals(expected, filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.dennisguse.opentracks.io.file;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
//TODO Merge with TrackFilenameGeneratorTest whenever Junit5 gets available.
|
||||
//https://github.com/android/android-test/issues/224
|
||||
@RunWith(Parameterized.class)
|
||||
public class TrackFilenameGeneratorTest2 {
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<String> data() {
|
||||
return Arrays.asList(
|
||||
"{name}_{starime}",
|
||||
"{name",
|
||||
"name}");
|
||||
}
|
||||
|
||||
private final TrackFilenameGenerator subject;
|
||||
|
||||
public TrackFilenameGeneratorTest2(String template) {
|
||||
this.subject = new TrackFilenameGenerator(template);
|
||||
}
|
||||
|
||||
@Test(expected = TrackFilenameGenerator.TemplateInvalidException.class)
|
||||
public void testFilenameTemplate() {
|
||||
// given
|
||||
Track track = new Track();
|
||||
track.setName("Best Track");
|
||||
track.setUuid(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
|
||||
track.getTrackStatistics().setStartTime(Instant.parse("2020-02-02T02:02:02Z"));
|
||||
|
||||
// when
|
||||
String filename = subject.format(track, TrackFileFormat.GPX);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -16,28 +17,36 @@ import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TimezoneRule;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
@@ -67,12 +76,18 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
//For csv_export_only() as we the timezone is hardcoded in the expectation.
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
@@ -132,7 +147,7 @@ public class ExportImportTest {
|
||||
|
||||
Distance sensorDistance = Distance.of(10); // recording distance interval
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 15, 10, 1);
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
|
||||
// A sensor-only TrackPoint
|
||||
@@ -143,24 +158,24 @@ public class ExportImportTest {
|
||||
trackPointCreator.setClock("2020-02-02T02:02:05Z");
|
||||
mockBLESensorData(trackPointCreator, 5f, Distance.of(2), 66f, 3f, 50f); // Distance will be added to next TrackPoint
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:05Z", 3, 14.001, 10, 15, 10, 0);
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:05Z", 3, 14.001, 10, 13, 15, 10, 0);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:06Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:20Z");
|
||||
service.resumeCurrentTrack();
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 15, 10, 0);
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:21Z", 3, 14.002, 10, 13, 15, 10, 0);
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:22Z", 3, 16, 10, 15, 10, 0);
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:22Z", 3, 16, 10, 13, 15, 10, 0);
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 15, 10, 0);
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:24Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator));
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
service.endCurrentTrack();
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -172,7 +187,7 @@ public class ExportImportTest {
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
markers = contentProviderUtils.getMarkers(trackId);
|
||||
assertEquals(11, trackPoints.size());
|
||||
assertEquals(10, trackPoints.size());
|
||||
assertEquals(2, markers.size());
|
||||
}
|
||||
|
||||
@@ -206,8 +221,7 @@ public class ExportImportTest {
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.noAccuracy();
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
a.assertEquals(trackPoints, actual);
|
||||
|
||||
@@ -320,8 +334,7 @@ public class ExportImportTest {
|
||||
// Therefore, the track segmentation is changes.
|
||||
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.setDelta(0.05)
|
||||
.noAccuracy(); // speed is not fully
|
||||
.setDelta(0.05); // speed is not fully
|
||||
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
@@ -330,7 +343,8 @@ public class ExportImportTest {
|
||||
.setAltitude(10)
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeLoss(1f)
|
||||
.setAltitudeGain(1f),
|
||||
.setAltitudeGain(1f)
|
||||
.setHorizontalAccuracy(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
|
||||
.setLatitude(3)
|
||||
.setLongitude(14.001)
|
||||
@@ -341,21 +355,24 @@ public class ExportImportTest {
|
||||
.setSensorDistance(Distance.of(12))
|
||||
.setHeartRate(66f)
|
||||
.setPower(50f)
|
||||
.setCadence(3f),
|
||||
.setCadence(3f)
|
||||
.setHorizontalAccuracy(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:21Z"))
|
||||
.setLatitude(3)
|
||||
.setLongitude(14.002)
|
||||
.setAltitude(10)
|
||||
.setAltitudeLoss(0f)
|
||||
.setAltitudeGain(0f)
|
||||
.setSpeed(Speed.of(15)),
|
||||
.setSpeed(Speed.of(15))
|
||||
.setHorizontalAccuracy(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2020-02-02T02:02:22Z"))
|
||||
.setLatitude(3)
|
||||
.setLongitude(16)
|
||||
.setAltitude(10)
|
||||
.setAltitudeLoss(0f)
|
||||
.setAltitudeGain(0f)
|
||||
.setSpeed(Speed.of(15)),
|
||||
.setSpeed(Speed.of(15))
|
||||
.setHorizontalAccuracy(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:23Z"))
|
||||
.setLatitude(3)
|
||||
.setLongitude(16.001)
|
||||
@@ -363,6 +380,7 @@ public class ExportImportTest {
|
||||
.setAltitudeLoss(0f)
|
||||
.setAltitudeGain(0f)
|
||||
.setSpeed(Speed.of(15))
|
||||
.setHorizontalAccuracy(Distance.of(10))
|
||||
), actual);
|
||||
|
||||
// 3. trackstatistics
|
||||
@@ -423,6 +441,37 @@ public class ExportImportTest {
|
||||
assertNull(trackImported);
|
||||
}
|
||||
|
||||
@Ignore(value = "TODO Fails on CI; works on API24 and API30 locally")
|
||||
@LargeTest
|
||||
@Test
|
||||
public void csv_export_only() throws TimeoutException, IOException {
|
||||
setUp();
|
||||
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// then
|
||||
InputStream expected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.csv_export);
|
||||
String expectedText = new BufferedReader(new InputStreamReader(expected, StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
|
||||
InputStream actual = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
String actualText = new BufferedReader(new InputStreamReader(actual, StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
assertEquals(expectedText, actualText);
|
||||
}
|
||||
|
||||
private void assertMarkers() {
|
||||
assertEquals(markers.size(), contentProviderUtils.getMarkers(importTrackId).size());
|
||||
|
||||
@@ -443,7 +492,7 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, trackPointCreator) {
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
|
||||
@Override
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
@@ -469,7 +518,7 @@ public class ExportImportTest {
|
||||
return sensorDataSet;
|
||||
}
|
||||
});
|
||||
trackPointCreator.onChange(null);
|
||||
trackPointCreator.onChange(new SensorDataSet());
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||
@@ -478,11 +527,14 @@ public class ExportImportTest {
|
||||
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
|
||||
}
|
||||
|
||||
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain) {
|
||||
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, float altitude, float altitudeGain) {
|
||||
Location location = new Location("mock");
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
location.setAccuracy(accuracy);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
location.setVerticalAccuracyMeters(verticalAccuracy);
|
||||
}
|
||||
location.setSpeed(speed);
|
||||
location.setAltitude(altitude);
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
public class TrackPointAssert {
|
||||
|
||||
private boolean assertAccuracy = true;
|
||||
|
||||
private double delta = 0.001;
|
||||
|
||||
public TrackPointAssert() {
|
||||
@@ -45,13 +43,13 @@ public class TrackPointAssert {
|
||||
Assert.assertEquals(expected.getSpeed().toMPS(), actual.getSpeed().toMPS(), delta);
|
||||
}
|
||||
|
||||
if (assertAccuracy) {
|
||||
Assert.assertEquals(expected.hasHorizontalAccuracy(), actual.hasHorizontalAccuracy());
|
||||
if (expected.hasHorizontalAccuracy()) {
|
||||
Assert.assertEquals(expected.getHorizontalAccuracy().toM(), actual.getHorizontalAccuracy().toM(), delta);
|
||||
}
|
||||
} else {
|
||||
Assert.assertFalse(actual.hasHorizontalAccuracy());
|
||||
Assert.assertEquals(expected.hasHorizontalAccuracy(), actual.hasHorizontalAccuracy());
|
||||
if (expected.hasHorizontalAccuracy()) {
|
||||
Assert.assertEquals(expected.getHorizontalAccuracy().toM(), actual.getHorizontalAccuracy().toM(), delta);
|
||||
}
|
||||
Assert.assertEquals(expected.hasVerticalAccuracy(), actual.hasVerticalAccuracy());
|
||||
if (expected.hasVerticalAccuracy()) {
|
||||
Assert.assertEquals(expected.getVerticalAccuracy().toM(), actual.getVerticalAccuracy().toM(), delta);
|
||||
}
|
||||
|
||||
Assert.assertEquals(expected.hasSensorDistance(), actual.hasSensorDistance());
|
||||
@@ -96,11 +94,6 @@ public class TrackPointAssert {
|
||||
}
|
||||
}
|
||||
|
||||
public TrackPointAssert noAccuracy() {
|
||||
this.assertAccuracy = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrackPointAssert setDelta(double delta) {
|
||||
this.delta = delta;
|
||||
return this;
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -48,7 +49,7 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
.thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
subject.setMetricUnits(true);
|
||||
subject.setUnitSystem(UnitSystem.METRIC);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, Distance.of(100));
|
||||
|
||||
@@ -612,6 +612,55 @@ public class TrackRecordingServiceTestRecording {
|
||||
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_gpsOnly_recordingDistance_movement_non_idle() {
|
||||
// given
|
||||
String startTime = "2020-02-02T02:02:02Z";
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
||||
|
||||
// when - will be ignored
|
||||
String gps2 = "2020-02-02T02:02:04Z";
|
||||
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 15);
|
||||
|
||||
// when
|
||||
String gps3 = "2020-02-02T02:02:05Z";
|
||||
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps3, 45.0001, 35.0, 1, 15);
|
||||
|
||||
// when
|
||||
String stopTime = "2020-02-02T02:02:12Z";
|
||||
trackPointCreator.setClock(stopTime);
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
new TrackPointAssert().assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setSpeed(Speed.of(15)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setSpeed(Speed.of(15)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
@@ -796,15 +845,12 @@ public class TrackRecordingServiceTestRecording {
|
||||
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor3))
|
||||
.setSpeed(Speed.of(5))
|
||||
.setSensorDistance(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.SENSORPOINT, Instant.parse(sensor5)) //TODO No need to store this TrackPoint, data could be merged into the next one
|
||||
.setSpeed(Speed.of(5))
|
||||
.setSensorDistance(Distance.of(4)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps3))
|
||||
.setLatitude(45.001)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(5))
|
||||
.setSensorDistance(Distance.of(0)),
|
||||
.setSensorDistance(Distance.of(4.0)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps4))
|
||||
.setLatitude(45.001)
|
||||
.setLongitude(35)
|
||||
|
||||
@@ -27,7 +27,7 @@ public class TrackRecordingServiceTestUtils {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, service.getTrackPointCreator()));
|
||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
|
||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
|
||||
@@ -54,7 +55,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Speed 18.4 kilometers per hour.", announcement);
|
||||
@@ -76,7 +77,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour.", announcement);
|
||||
@@ -92,7 +93,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Pace 3 minutes 15 seconds per kilometer.", announcement);
|
||||
@@ -114,7 +115,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, false, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Pace 1 minute 10 seconds per kilometer. Lap time 1 minute 10 seconds per kilometer.", announcement);
|
||||
@@ -130,7 +131,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Speed 11.4 miles per hour.", announcement);
|
||||
@@ -152,7 +153,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, true, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement);
|
||||
@@ -168,7 +169,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, null, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement);
|
||||
@@ -190,7 +191,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval, null).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.IMPERIAL, false, lastInterval, null).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement);
|
||||
@@ -215,7 +216,7 @@ public class VoiceAnnouncementUtilsTest {
|
||||
SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null);
|
||||
|
||||
// when
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, sensorStatistics).toString();
|
||||
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, UnitSystem.METRIC, true, lastInterval, sensorStatistics).toString();
|
||||
|
||||
// then
|
||||
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -62,7 +61,7 @@ public class GPSHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class));
|
||||
verify(trackPointCreator, times(1)).onChange(any(Location.class));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +77,7 @@ public class GPSHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class));
|
||||
verify(trackPointCreator, times(0)).onChange(any(Location.class));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +93,7 @@ public class GPSHandlerTest {
|
||||
|
||||
// then
|
||||
// no newTrackPoint called
|
||||
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class));
|
||||
verify(trackPointCreator, times(0)).onChange(any(Location.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,7 +106,7 @@ public class GPSHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class));
|
||||
verify(trackPointCreator, times(1)).onChange(any(Location.class));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,7 @@ public class TrackPointCreatorTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new TrackPointCreator(locationHandler, server);
|
||||
subject.start(context);
|
||||
subject.start(context, null);
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -58,7 +58,7 @@ public class PreferencesUtilsTest {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ public class PreferencesUtilsTest {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -59,13 +59,13 @@ public class EspressoAggregatedFilterTest {
|
||||
@Test
|
||||
public void espressoAggregatedFilterTest() {
|
||||
// open AggregatedStatisticsActivity through toolbar's menu item
|
||||
onView(ViewMatchers.withId(R.id.toolbar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.toolbar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
|
||||
onView(ViewMatchers.withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.track_list_aggregated_stats))));
|
||||
onView(withId(R.id.track_list_aggregated_stats)).perform(click());
|
||||
|
||||
// open FilterDialogFragment through toolbar's menu item
|
||||
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.toolbar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(isDisplayed()));
|
||||
onView(withId(R.id.bottom_app_bar)).check(matches(hasDescendant(withId(R.id.aggregated_statistics_filter))));
|
||||
onView(withId(R.id.aggregated_statistics_filter)).perform(click());
|
||||
|
||||
// check there's a checkbox with CATEGORY text
|
||||
|
||||
@@ -41,7 +41,7 @@ public class EspressoCustomLayoutTest {
|
||||
@Test
|
||||
public void customLayoutTest() {
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// Get custom layout preferences and check all data fields are showed.
|
||||
@@ -50,7 +50,7 @@ public class EspressoCustomLayoutTest {
|
||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
||||
|
||||
// stop recording
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.LocaleRule;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringUtils}.
|
||||
@@ -91,21 +89,6 @@ public class StringUtilsTest {
|
||||
assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(360000000)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatDistance(android.content.Context, Distance, boolean)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFormatDistance() {
|
||||
// A large number in metric
|
||||
assertEquals("5.00 km", StringUtils.formatDistance(context, Distance.of(5000), true));
|
||||
// A large number in imperial
|
||||
assertEquals("3.11 mi", StringUtils.formatDistance(context, Distance.of(5000), false));
|
||||
// A small number in metric
|
||||
assertEquals("100.00 m", StringUtils.formatDistance(context, Distance.of(100), true));
|
||||
// A small number in imperial
|
||||
assertEquals("328.08 ft", StringUtils.formatDistance(context, Distance.of(100), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatCData(String)}.
|
||||
*/
|
||||
@@ -183,27 +166,6 @@ public class StringUtilsTest {
|
||||
assertEquals("1.0", StringUtils.formatDecimal(0.99, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSpeedParts() {
|
||||
assertEquals("4:59", StringUtils.getSpeedParts(context, Speed.of(3.34), true, false).first);
|
||||
assertEquals("5:00", StringUtils.getSpeedParts(context, Speed.of(3.33), true, false).first);
|
||||
|
||||
assertEquals("11.9", StringUtils.getSpeedParts(context, Speed.of(3.31), true, true).first);
|
||||
assertEquals("7.5", StringUtils.getSpeedParts(context, Speed.of(3.34), false, true).first);
|
||||
|
||||
assertEquals("min/km", StringUtils.getSpeedParts(context, Speed.zero(), true, false).second);
|
||||
assertEquals("min/mi", StringUtils.getSpeedParts(context, Speed.zero(), false, false).second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSpeed() {
|
||||
assertEquals("4:59 min/km", StringUtils.formatSpeed(context, Speed.of(3.34), true, false));
|
||||
assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, Speed.of(3.34), false, false));
|
||||
|
||||
assertEquals("12.0 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true));
|
||||
assertEquals("7.5 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatDateTodayRelative() {
|
||||
// given
|
||||
|
||||
@@ -23,6 +23,7 @@ import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
@@ -61,7 +62,7 @@ public class StatisticDataBuilderTest extends TestCase {
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 3);
|
||||
@@ -100,7 +101,7 @@ public class StatisticDataBuilderTest extends TestCase {
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 18);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#time,trackpoint_type,latitude,longitude,altitude,accuracy_horizontal,accuracy_vertical,speed,altitude_gain,altitude_loss,sensor_distance,heartrate,cadence,power
|
||||
"2020-02-02T03:02:02+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:03+01:00","TRACKPOINT",3,3,10,10,,54,1,1,,,,
|
||||
"2020-02-02T03:02:04+01:00","SENSORPOINT",,,,,,54,1,1,10,66,3,50
|
||||
"2020-02-02T03:02:05+01:00","TRACKPOINT",3,3,10,10,,18,0,0,2,66,3,50
|
||||
"2020-02-02T03:02:06+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
|
||||
"2020-02-02T03:02:21+01:00","TRACKPOINT",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:22+01:00","SEGMENT_START_AUTOMATIC",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:23+01:00","TRACKPOINT",3,3,10,10,,54,0,0,,,,
|
||||
"2020-02-02T03:02:24+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
|
||||
|
@@ -77,14 +77,14 @@ limitations under the License.
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- <activity-->
|
||||
<!-- android:name=".publicapi.StopRecording"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:theme="@style/SplashTheme">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </activity>-->
|
||||
<activity
|
||||
android:name=".publicapi.StopRecording"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
@@ -324,6 +324,7 @@ limitations under the License.
|
||||
</activity>
|
||||
|
||||
<activity android:name=".TrackRecordedActivity" />
|
||||
<activity android:name=".TrackStoppedActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackRecordingActivity"
|
||||
@@ -363,6 +364,14 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ShowErrorActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:process=":crash"/>
|
||||
|
||||
<provider
|
||||
android:name=".data.ShareContentProvider"
|
||||
android:authorities="${applicationId}.content"
|
||||
|
||||
@@ -24,6 +24,8 @@ public class AboutActivity extends AbstractActivity {
|
||||
viewBinding.aboutTextVersionCode.setText(getString(R.string.about_version_code, SystemUtils.getAppVersionCode(this)));
|
||||
viewBinding.aboutAppUrl.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
|
||||
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,7 @@ package de.dennisguse.opentracks;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import de.dennisguse.opentracks.services.announcement.VoiceAnnouncement;
|
||||
|
||||
@@ -38,9 +36,6 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
||||
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
|
||||
|
||||
setContentView(getRootView());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setupActionBarBack(toolbar);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,12 +45,4 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
protected abstract View getRootView();
|
||||
|
||||
protected void setupActionBarBack(@Nullable Toolbar toolbar) {
|
||||
setSupportActionBar(toolbar);
|
||||
if (toolbar != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Track controller for record, pause, resume, and stop.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Move buttons into separate components
|
||||
public class ControllerFragment extends Fragment implements View.OnTouchListener {
|
||||
|
||||
private static final String TAG = ControllerFragment.class.getSimpleName();
|
||||
|
||||
private Handler handlerUpdateTotalTime;
|
||||
|
||||
private Runnable buttonDelay;
|
||||
private int buttonDelayDuration;
|
||||
|
||||
private ControllerFragmentBinding viewBinding;
|
||||
private TransitionDrawable transition;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private RecordingStatus recordingStatus;
|
||||
private RecordingData recordingData;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(ControllerFragment.this, this::onRecordingStatusChanged);
|
||||
service.getRecordingDataObservable()
|
||||
.observe(ControllerFragment.this, this::onTotalTimeChanged);
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
buttonDelayDuration = getResources().getInteger(R.integer.buttonDelayMillis);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
handlerUpdateTotalTime = new Handler();
|
||||
recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
viewBinding = ControllerFragmentBinding.inflate(inflater, container, false);
|
||||
|
||||
viewBinding.controllerRecord.setOnTouchListener(this);
|
||||
viewBinding.controllerRecord.setOnClickListener((view) -> {
|
||||
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Callback callback = (Callback) getContext();
|
||||
if (callback != null) {
|
||||
callback.recordStart();
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.controllerStop.setOnTouchListener(this);
|
||||
transition = (TransitionDrawable) viewBinding.controllerContainer.getBackground();
|
||||
|
||||
onRecordingStatusChanged(recordingStatus);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
handlerUpdateTotalTime.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
viewBinding = null;
|
||||
transition = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
handlerUpdateTotalTime = null;
|
||||
recordingStatus = null;
|
||||
}
|
||||
|
||||
//TODO Move into a separate object
|
||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||
@Override
|
||||
public boolean onTouch(final View view, MotionEvent event) {
|
||||
if (!isResumed()) {
|
||||
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
||||
return false;
|
||||
}
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||
buttonDelay = null;
|
||||
view.setPressed(false);
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
showStatusSetDefaultText();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Trigger button pressed animation
|
||||
view.setPressed(true);
|
||||
|
||||
//To pause a recording
|
||||
if (viewBinding.controllerRecord.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& recordingStatus.isRecordingAndNotPaused()) {
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 150);
|
||||
viewBinding.controllerStatus.setText(R.string.hold_to_pause);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
view.performClick();
|
||||
Callback callback = (Callback) getContext();
|
||||
if (callback != null) {
|
||||
callback.recordPause();
|
||||
}
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 1000);
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
//To stop a recording
|
||||
if (viewBinding.controllerStop.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& recordingStatus.isRecording()) {
|
||||
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(getContext(), 150);
|
||||
viewBinding.controllerStatus.setText(R.string.hold_to_stop);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
Context context = getContext();
|
||||
Callback callback = (Callback) context;
|
||||
if (callback != null) {
|
||||
callback.recordStop();
|
||||
ActivityUtils.vibrate(context, 1000);
|
||||
}
|
||||
|
||||
transition.resetTransition();
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
viewBinding.controllerContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
viewBinding.controllerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void showStatusSetDefaultText() {
|
||||
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void recordStart();
|
||||
|
||||
void recordPause();
|
||||
|
||||
void recordStop();
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
|
||||
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.controllerRecord.setContentDescription(getContext().getString(recordingStatus.isRecordingAndNotPaused() ? R.string.image_pause : R.string.image_record));
|
||||
|
||||
viewBinding.controllerStop.setEnabled(recordingStatus.isRecording());
|
||||
|
||||
viewBinding.controllerStatus.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||
if (recordingStatus.isRecording()) {
|
||||
viewBinding.controllerStatus.setTextColor(getContext().getResources().getColor(recordingStatus.isPaused() ? android.R.color.white : R.color.recording_text));
|
||||
showStatusSetDefaultText();
|
||||
}
|
||||
|
||||
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void onTotalTimeChanged(RecordingData recordingData) {
|
||||
this.recordingData = recordingData;
|
||||
|
||||
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(this.recordingData.getTrackStatistics().getTotalTime()));
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,18 @@ import de.dennisguse.opentracks.ui.util.ViewUtils;
|
||||
|
||||
public class HelpActivity extends AbstractActivity {
|
||||
|
||||
private HelpBinding helpBinding;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setSupportActionBar(helpBinding.bottomAppBarLayout.bottomAppBar);
|
||||
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
return HelpBinding.inflate(getLayoutInflater()).getRoot();
|
||||
helpBinding = HelpBinding.inflate(getLayoutInflater());
|
||||
return helpBinding.getRoot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
|
||||
public class ShowErrorActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_ERROR_TEXT = "error";
|
||||
|
||||
private ActivityShowErrorBinding viewBinding;
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding.textViewError.setText(getIntent().getStringExtra(EXTRA_ERROR_TEXT));
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(createErrorTitle());
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = ActivityShowErrorBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private String createErrorTitle() {
|
||||
return String.format(getString(R.string.error_crash_title), getString(R.string.app_name));
|
||||
}
|
||||
|
||||
private void reportBug() {
|
||||
Uri uriUrl;
|
||||
try {
|
||||
uriUrl = Uri.parse(
|
||||
String.format(
|
||||
getString(R.string.report_issue_link),
|
||||
URLEncoder.encode(viewBinding.textViewError.getText().toString(), StandardCharsets.UTF_8.toString())
|
||||
)
|
||||
);
|
||||
} catch (final UnsupportedEncodingException ignored) {
|
||||
// can't happen as UTF-8 is always available
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.show_error, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
if (item.getItemId() == R.id.error_share) {
|
||||
onClickedShare();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.error_report) {
|
||||
reportBug();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onClickedShare() {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, createErrorTitle());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, viewBinding.textViewError.getText());
|
||||
intent.setType("text/plain");
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* Code that is executed when the application starts.
|
||||
@@ -28,9 +32,7 @@ public class Startup extends Application {
|
||||
PreferencesUtils.initPreferences(this, getResources());
|
||||
// Set default values of preferences on first start.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString( R.string.stats_units_key, "").equals("")) {
|
||||
PreferencesUtils.setMetricUnits(!Locale.US.equals(Locale.getDefault()));
|
||||
}
|
||||
PreferencesUtils.applyDefaultUnit();
|
||||
PreferencesUtils.applyNightMode();
|
||||
|
||||
|
||||
@@ -41,6 +43,36 @@ public class Startup extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
// handle crashes only outside the crash reporter activity/process
|
||||
if (!isCrashReportingProcess()) {
|
||||
Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
ExceptionHandler crashReporter = new ExceptionHandler(this, defaultPlatformHandler);
|
||||
Thread.setDefaultUncaughtExceptionHandler(crashReporter);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCrashReportingProcess() {
|
||||
String processName = "";
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
// Using the same technique as Application.getProcessName() for older devices
|
||||
// Using reflection since ActivityThread is an internal API
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
Class<?> activityThread = Class.forName("android.app.ActivityThread");
|
||||
@SuppressLint("DiscouragedPrivateApi") Method getProcessName = activityThread.getDeclaredMethod("currentProcessName");
|
||||
processName = (String) getProcessName.invoke(null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
} else {
|
||||
processName = Application.getProcessName();
|
||||
}
|
||||
return processName != null && processName.endsWith(":crash");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the database used by SQLiteOpenHelper.
|
||||
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
|
||||
|
||||
@@ -71,17 +71,17 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.trackEditName.setText(track.getName());
|
||||
viewBinding.fields.trackEditName.setText(track.getName());
|
||||
|
||||
viewBinding.trackEditActivityType.setText(track.getCategory());
|
||||
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
viewBinding.trackEditActivityType.setAdapter(adapter);
|
||||
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
|
||||
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
||||
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
||||
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackEditActivity.this, viewBinding.trackEditActivityType.getText().toString()));
|
||||
TrackEditActivity.this, viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,22 +94,23 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
}
|
||||
|
||||
setActivityTypeIcon(iconValue);
|
||||
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.trackEditActivityType.getText().toString()));
|
||||
viewBinding.fields.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||
|
||||
viewBinding.trackEditDescription.setText(track.getDescription());
|
||||
viewBinding.fields.trackEditDescription.setText(track.getDescription());
|
||||
|
||||
viewBinding.trackEditSave.setOnClickListener(v -> {
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, viewBinding.trackEditName.getText().toString(),
|
||||
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
|
||||
TrackUtils.updateTrack(TrackEditActivity.this, track, viewBinding.fields.trackEditName.getText().toString(),
|
||||
viewBinding.fields.trackEditActivityType.getText().toString(), viewBinding.fields.trackEditDescription.getText().toString(),
|
||||
contentProviderUtils);
|
||||
finish();
|
||||
});
|
||||
|
||||
setTitle(R.string.menu_edit);
|
||||
viewBinding.trackEditCancel.setOnClickListener(v -> finish());
|
||||
viewBinding.trackEditCancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_edit));
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
@@ -137,12 +138,12 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
private void setActivityTypeIcon(String iconValue) {
|
||||
this.iconValue = iconValue;
|
||||
viewBinding.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
||||
viewBinding.fields.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseActivityTypeDone(String value) {
|
||||
setActivityTypeIcon(value);
|
||||
viewBinding.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
|
||||
viewBinding.fields.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,17 +25,19 @@ import android.database.Cursor;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
@@ -49,6 +51,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
@@ -58,6 +61,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.share.ShareUtils;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.AggregatedStatisticsActivity;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||
@@ -74,7 +78,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, ControllerFragment.Callback {
|
||||
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||
|
||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||
|
||||
@@ -87,7 +91,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
private final TrackLoaderCallBack loaderCallbacks = new TrackLoaderCallBack();
|
||||
|
||||
// Preferences
|
||||
private boolean metricUnits = true;
|
||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||
|
||||
private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT;
|
||||
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
@@ -99,6 +103,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showSelectAll) {
|
||||
boolean isSingleSelection = trackIds.length == 1;
|
||||
|
||||
viewBinding.bottomAppBar.performHide(true);
|
||||
viewBinding.trackListFabAction.setVisibility(View.INVISIBLE);
|
||||
|
||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||
}
|
||||
@@ -107,11 +114,17 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
public boolean onClick(int itemId, int[] positions, long[] trackIds) {
|
||||
return handleContextItem(itemId, trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
viewBinding.trackListFabAction.setVisibility(View.VISIBLE);
|
||||
viewBinding.bottomAppBar.performShow(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits();
|
||||
unitSystem = PreferencesUtils.getUnitSystem();
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
@@ -125,7 +138,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackListActivity.this, this::onRecordingStatusChanged);
|
||||
|
||||
@@ -153,16 +166,17 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
if (recordingStatus.isRecording() && trackId == recordingStatus.getTrackId().getId()) {
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackIdId) -> {
|
||||
Track.Id trackId = new Track.Id(trackIdId);
|
||||
if (recordingStatus.isRecording() && trackId.equals(recordingStatus.getTrackId())) {
|
||||
// Is recording -> open record activity.
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
this,
|
||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
@@ -190,14 +204,16 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
int iconId = TrackIconUtils.getIconDrawable(icon);
|
||||
String name = cursor.getString(nameIndex);
|
||||
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
|
||||
String totalDistance = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(TrackListActivity.this).formatDistance(Distance.of(cursor.getDouble(totalDistanceIndex)));
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
|
||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingStatus.isPaused(),
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording,
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
OffsetDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneOffset.ofTotalSeconds(startTimeOffset)),
|
||||
category, description, false);
|
||||
@@ -206,13 +222,41 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||
ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback);
|
||||
|
||||
loadData(getIntent());
|
||||
}
|
||||
viewBinding.trackListFabAction.setOnClickListener((view) -> {
|
||||
if (recordingStatus.isRecording()) {
|
||||
Toast.makeText(TrackListActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupActionBarBack(Toolbar toolbar) {
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||
// Not Recording -> Recording
|
||||
updateGpsMenuItem(false, true);
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
|
||||
connection.unbind(this);
|
||||
}).startAndBind(this);
|
||||
});
|
||||
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
|
||||
if (!recordingStatus.isRecording()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recording -> Stop
|
||||
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||
updateGpsMenuItem(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||
viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24);
|
||||
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark));
|
||||
return true;
|
||||
});
|
||||
|
||||
setSupportActionBar(viewBinding.bottomAppBar);
|
||||
|
||||
loadData(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,6 +274,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||
|
||||
// Float button
|
||||
setFloatButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,6 +306,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
|
||||
|
||||
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
|
||||
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white));
|
||||
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
|
||||
|
||||
searchView.setOnCloseListener(() -> {
|
||||
searchView.clearFocus();
|
||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
@@ -511,38 +565,18 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
if (recordingStatus.getTrackId() == null) {
|
||||
// Not recording -> Recording
|
||||
updateGpsMenuItem(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingStatus.isPaused()) {
|
||||
// Paused -> Resume
|
||||
updateGpsMenuItem(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordPause() {
|
||||
updateGpsMenuItem(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
updateGpsMenuItem(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||
}
|
||||
|
||||
public void onGpsStatusChanged(GpsStatusValue newStatus) {
|
||||
gpsStatusValue = newStatus;
|
||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||
}
|
||||
|
||||
private void setFloatButton() {
|
||||
viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24);
|
||||
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks_secondary_color : R.color.red_dark));
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
setFloatButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindCallback = service -> service.getRecordingStatusObservable()
|
||||
private final TrackRecordingServiceConnection.Callback bindCallback = (service, unused) -> service.getRecordingStatusObservable()
|
||||
.observe(TrackRecordedActivity.this, this::onRecordingStatusChanged);
|
||||
|
||||
@Override
|
||||
@@ -102,6 +102,11 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(track != null ? track.getName() : "");
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.replaceMenu(R.menu.track_detail);
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
@@ -162,8 +167,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!recordingStatus.isRecording());
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
setTitle(track != null ? track.getName() : "");
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -200,11 +203,17 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_resume_track) {
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
finish();
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -246,7 +255,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
private void handleIntent(Intent intent) {
|
||||
trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (trackId == null) {
|
||||
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
|
||||
Log.e(TAG, TrackRecordedActivity.class.getSimpleName() + " needs EXTRA_TRACK_ID.");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -13,9 +14,8 @@ import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -54,9 +54,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||
//This mode of communication should be removed anyhow.
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface, ControllerFragment.Callback {
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackDataHubInterface {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
@@ -73,13 +71,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
private TrackRecordingBinding viewBinding;
|
||||
|
||||
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
|
||||
@Deprecated
|
||||
private Track.Id trackId;
|
||||
|
||||
private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = service -> {
|
||||
private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> {
|
||||
service.getRecordingStatusObservable()
|
||||
.observe(TrackRecordingActivity.this, this::onRecordingStatusChanged);
|
||||
|
||||
@@ -87,18 +83,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
|
||||
|
||||
if (!service.isRecording()) {
|
||||
if (trackId == null) {
|
||||
// trackId isn't initialized -> leads a new recording.
|
||||
trackId = service.startNewTrack();
|
||||
} else {
|
||||
// trackId is initialized -> resumes the track.
|
||||
service.resumeTrack(trackId);
|
||||
}
|
||||
|
||||
// A recording track is on.
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
};
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
@@ -116,24 +106,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
trackId = null;
|
||||
if (savedInstanceState != null) {
|
||||
//Activity was recreated.
|
||||
trackId = savedInstanceState.getParcelable(EXTRA_TRACK_ID);
|
||||
} else {
|
||||
// Resume a track
|
||||
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (trackId != null && contentProviderUtils.getTrack(trackId) == null) {
|
||||
Log.w(TAG, "TrackId does not exists; cannot continue the recording.");
|
||||
finish();
|
||||
}
|
||||
|
||||
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (trackId == null) {
|
||||
throw new RuntimeException("TrackId is mandatory");
|
||||
}
|
||||
if (contentProviderUtils.getTrack(trackId) == null) {
|
||||
Log.w(TAG, "TrackId does not exists.");
|
||||
finish();
|
||||
}
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
@@ -148,6 +132,26 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
requestGPSPermissions();
|
||||
|
||||
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
|
||||
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setBackgroundColor(getResources().getColor(R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
|
||||
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackStoppedActivity.class)
|
||||
.putExtra(TrackStoppedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
finish();
|
||||
return true;
|
||||
});
|
||||
viewBinding.trackRecordingFabAction.setOnClickListener((view) -> {
|
||||
Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
|
||||
viewBinding.bottomAppBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24);
|
||||
setSupportActionBar(viewBinding.bottomAppBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,7 +224,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(CURRENT_TAB_TAG_KEY, viewBinding.trackDetailActivityViewPager.getCurrentItem());
|
||||
outState.putParcelable(EXTRA_TRACK_ID, trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,19 +250,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.track_record, menu);
|
||||
|
||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
updateMenuItems(recordingStatus.isPaused());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
@@ -315,15 +308,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
return trackDataHub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu items.
|
||||
*/
|
||||
private void updateMenuItems(boolean isPaused) {
|
||||
insertMarkerMenuItem.setVisible(!isPaused);
|
||||
markerListMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
|
||||
setTitle(getString(isPaused ? R.string.generic_paused : R.string.generic_recording));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseActivityTypeDone(String iconValue) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -331,29 +315,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
updateMenuItems(false);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordPause() {
|
||||
updateMenuItems(true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
updateMenuItems(true);
|
||||
finish();
|
||||
}
|
||||
|
||||
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
|
||||
|
||||
public CustomFragmentPagerAdapter(@NonNull FragmentActivity fa) {
|
||||
@@ -399,6 +360,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
if (!status.isRecording()) {
|
||||
finish();
|
||||
}
|
||||
recordingStatus = status;
|
||||
|
||||
trackDataHub.setRecordingStatus(recordingStatus);
|
||||
@@ -412,17 +376,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
return;
|
||||
}
|
||||
|
||||
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
|
||||
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
||||
Boolean coarseLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
|
||||
if (fineLocationGranted == null || !fineLocationGranted
|
||||
|| coarseLocationGranted == null || !coarseLocationGranted) {
|
||||
Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
);
|
||||
String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
|
||||
locationPermissionRequest.launch(permissions);
|
||||
PermissionUtils.requestGPSPermission(this, null, () -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
||||
@@ -437,7 +391,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
.make(viewBinding.trackRecordingCoordinatorLayout,
|
||||
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(getString(R.string.generic_dismiss), v -> {});
|
||||
.setAction(getString(R.string.generic_dismiss), v -> {
|
||||
});
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackStoppedBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
public class TrackStoppedActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
|
||||
|
||||
private static final String TAG = TrackStoppedActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
private TrackStoppedBinding viewBinding;
|
||||
|
||||
private Track.Id trackId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (trackId == null) {
|
||||
Log.e(TAG, "TrackStoppedActivity needs EXTRA_TRACK_ID.");
|
||||
finish();
|
||||
}
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
viewBinding.fields.trackEditName.setText(track.getName());
|
||||
|
||||
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
|
||||
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
|
||||
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
|
||||
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
|
||||
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
setActivityTypeIcon(TrackIconUtils.getIconValue(
|
||||
TrackStoppedActivity.this, viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
String iconValue = track.getIcon();
|
||||
|
||||
setActivityTypeIcon(iconValue);
|
||||
viewBinding.fields.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), viewBinding.fields.trackEditActivityType.getText().toString()));
|
||||
|
||||
viewBinding.fields.trackEditDescription.setText(track.getDescription());
|
||||
|
||||
viewBinding.time.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
|
||||
|
||||
{
|
||||
Pair<String, String> parts = SpeedFormatter.Builder()
|
||||
.setUnit(PreferencesUtils.getUnitSystem())
|
||||
.setReportSpeedOrPace(PreferencesUtils.isReportSpeed(track.getCategory()))
|
||||
.build(this)
|
||||
.getSpeedParts(track.getTrackStatistics().getAverageMovingSpeed());
|
||||
viewBinding.speed.setText(parts.first);
|
||||
viewBinding.speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = DistanceFormatter.Builder()
|
||||
.setUnit(PreferencesUtils.getUnitSystem())
|
||||
.build(this)
|
||||
.getDistanceParts(track.getTrackStatistics().getTotalDistance());
|
||||
viewBinding.distance.setText(parts.first);
|
||||
viewBinding.distanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
viewBinding.finishButton.setOnClickListener(v -> {
|
||||
TrackUtils.updateTrack(TrackStoppedActivity.this, track, viewBinding.fields.trackEditName.getText().toString(),
|
||||
viewBinding.fields.trackEditActivityType.getText().toString(), viewBinding.fields.trackEditDescription.getText().toString(),
|
||||
contentProviderUtils);
|
||||
ExportUtils.postWorkoutExport(this, trackId);
|
||||
finish();
|
||||
});
|
||||
|
||||
viewBinding.resumeButton.setOnClickListener(v -> {
|
||||
resumeTrackAndFinish();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
resumeTrackAndFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackStoppedBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private void setActivityTypeIcon(String iconValue) {
|
||||
viewBinding.fields.trackEditActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(iconValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChooseActivityTypeDone(String iconValue) {
|
||||
setActivityTypeIcon(iconValue);
|
||||
viewBinding.fields.trackEditActivityType.setText(getString(TrackIconUtils.getIconActivityType(iconValue)));
|
||||
}
|
||||
|
||||
private void resumeTrackAndFinish() {
|
||||
new TrackRecordingServiceConnection((service, connection) -> {
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
Intent newIntent = IntentUtils.newIntent(TrackStoppedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
|
||||
@@ -77,9 +78,9 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits();
|
||||
if (metricUnits != viewBinding.chartView.getMetricUnits()) {
|
||||
viewBinding.chartView.setMetricUnits(metricUnits);
|
||||
UnitSystem unitSystem = PreferencesUtils.getUnitSystem();
|
||||
if (unitSystem != viewBinding.chartView.getUnitSystem()) {
|
||||
viewBinding.chartView.setUnitSystem(unitSystem);
|
||||
runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
viewBinding.chartView.requestLayout();
|
||||
@@ -189,7 +190,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener {
|
||||
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, Altitude smoothedAltitude) {
|
||||
if (isResumed()) {
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude, chartByDistance, viewBinding.chartView.getMetricUnits());
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude, chartByDistance, viewBinding.chartView.getUnitSystem());
|
||||
pendingPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.models.UnitConversions;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
public class ChartPoint {
|
||||
@@ -28,20 +29,20 @@ public class ChartPoint {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, Altitude smoothedAltitude, boolean chartByDistance, boolean metricUnits) {
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, Altitude smoothedAltitude, boolean chartByDistance, UnitSystem unitSystem) {
|
||||
if (chartByDistance) {
|
||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(metricUnits);
|
||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(unitSystem);
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
|
||||
if (smoothedAltitude != null) {
|
||||
altitude = Distance.of(smoothedAltitude.toM()).toM_FT(metricUnits);
|
||||
altitude = Distance.of(smoothedAltitude.toM()).toM_FT(unitSystem);
|
||||
}
|
||||
|
||||
if (smoothedSpeed != null) {
|
||||
speed = smoothedSpeed.to(metricUnits);
|
||||
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
speed = smoothedSpeed.to(unitSystem);
|
||||
pace = smoothedSpeed.toPace(unitSystem).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
}
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = (double) trackPoint.getHeartRate().getBPM();
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
|
||||
/**
|
||||
@@ -217,11 +218,15 @@ abstract class ChartValueSeries {
|
||||
return maxMarkerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title id.
|
||||
*/
|
||||
int getTitleId(boolean metricUnits) {
|
||||
return metricUnits ? metricTitleId : imperialTitleId;
|
||||
int getTitleId(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return metricTitleId;
|
||||
case IMPERIAL:
|
||||
return imperialTitleId;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,7 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerDetailActivity;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
@@ -116,7 +117,7 @@ public class ChartView extends View {
|
||||
private int effectiveHeight = 0;
|
||||
|
||||
private boolean chartByDistance = false;
|
||||
private boolean metricUnits = true;
|
||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||
private boolean reportSpeed = true;
|
||||
private boolean showPointer = false;
|
||||
|
||||
@@ -371,17 +372,12 @@ public class ChartView extends View {
|
||||
this.chartByDistance = chartByDistance;
|
||||
}
|
||||
|
||||
public boolean getMetricUnits() {
|
||||
return metricUnits;
|
||||
public UnitSystem getUnitSystem() {
|
||||
return unitSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets metric units.
|
||||
*
|
||||
* @param value true to use metric units
|
||||
*/
|
||||
public void setMetricUnits(boolean value) {
|
||||
metricUnits = value;
|
||||
public void setUnitSystem(UnitSystem value) {
|
||||
unitSystem = value;
|
||||
}
|
||||
|
||||
public boolean getReportSpeed() {
|
||||
@@ -677,7 +673,7 @@ public class ChartView extends View {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
count++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(unitSystem));
|
||||
Paint paint = chartValueSeries.getTitlePaint();
|
||||
int x = (int) (0.5 * width) + getScrollX();
|
||||
int y = topBorder - spacer - (lines - count) * (lineHeight + spacer);
|
||||
@@ -696,7 +692,7 @@ public class ChartView extends View {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
lines++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(unitSystem));
|
||||
Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
|
||||
if (rect.height() > lineHeight) {
|
||||
lineHeight = rect.height();
|
||||
@@ -731,7 +727,14 @@ public class ChartView extends View {
|
||||
private String getXAxisLabel() {
|
||||
Context context = getContext();
|
||||
if (chartByDistance) {
|
||||
return metricUnits ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile);
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return context.getString(R.string.unit_kilometer);
|
||||
case IMPERIAL:
|
||||
return context.getString(R.string.unit_mile);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
} else {
|
||||
return context.getString(R.string.description_time);
|
||||
}
|
||||
@@ -986,7 +989,7 @@ public class ChartView extends View {
|
||||
|
||||
private double getMarkerXValue(Marker marker) {
|
||||
if (chartByDistance) {
|
||||
return marker.getLength().toKM_Miles(metricUnits);
|
||||
return marker.getLength().toKM_Miles(unitSystem);
|
||||
} else {
|
||||
return marker.getDuration().toMillis();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class CachedTrackPointsIndexes {
|
||||
final int timeIndex;
|
||||
final int altitudeIndex;
|
||||
final int accuracyIndex;
|
||||
final int accuracyVerticalIndex;
|
||||
final int speedIndex;
|
||||
final int bearingIndex;
|
||||
final int sensorHeartRateIndex;
|
||||
@@ -32,6 +33,7 @@ class CachedTrackPointsIndexes {
|
||||
timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
|
||||
altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
|
||||
accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.HORIZONTAL_ACCURACY);
|
||||
accuracyVerticalIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.VERTICAL_ACCURACY);
|
||||
speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
|
||||
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
|
||||
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
||||
|
||||
@@ -573,6 +573,9 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||
trackPoint.setHorizontalAccuracy(Distance.of(cursor.getFloat(indexes.accuracyIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.accuracyVerticalIndex)) {
|
||||
trackPoint.setVerticalAccuracy(Distance.of(cursor.getFloat(indexes.accuracyVerticalIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex)));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
private static final int DATABASE_VERSION = 33;
|
||||
private static final int DATABASE_VERSION = 34;
|
||||
|
||||
public CustomSQLiteOpenHelper(Context context) {
|
||||
this(context, ((Startup) context.getApplicationContext()).getDatabaseName());
|
||||
@@ -91,6 +91,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 33:
|
||||
upgradeFrom32to33(db);
|
||||
break;
|
||||
case 34:
|
||||
upgradeFrom33to34(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
@@ -133,6 +136,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 32:
|
||||
downgradeFrom33to32(db);
|
||||
break;
|
||||
case 33:
|
||||
downgradeFrom34to33(db);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
}
|
||||
@@ -206,7 +212,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE tracks ADD COLUMN uuid BLOB");
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id"}, null, null, null, null, null)) {
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id" }, null, null, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
|
||||
do {
|
||||
@@ -396,7 +402,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.beginTransaction();
|
||||
|
||||
// TrackPoints
|
||||
//TODO That does not restore deleted trackpoints
|
||||
// Info: this does not restore deleted trackpoints
|
||||
db.execSQL("UPDATE trackpoints SET latitude = 200 * 1E6, longitude = NULL WHERE type = -2");
|
||||
db.execSQL("UPDATE trackpoints SET latitude = 100 * 1E6, longitude = NULL WHERE type = 1");
|
||||
|
||||
@@ -481,7 +487,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
ZoneRules zoneRules = ZoneOffset.systemDefault().getRules();
|
||||
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id", "starttime"}, null, null, null, null, null)) {
|
||||
try (Cursor cursor = db.query("tracks", new String[]{"_id", "starttime" }, null, null, null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
|
||||
int startTimeIndex = cursor.getColumnIndexOrThrow("starttime");
|
||||
@@ -513,6 +519,33 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL("CREATE UNIQUE INDEX tracks_uuid_index ON tracks(uuid)");
|
||||
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add accuracy_vertical
|
||||
*/
|
||||
private void upgradeFrom33to34(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE trackpoints ADD COLUMN accuracy_vertical FLOAT");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom34to33(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
|
||||
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), sensor_distance FLOAT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type, sensor_distance FROM trackpoints_old");
|
||||
db.execSQL("DROP TABLE trackpoints_old");
|
||||
|
||||
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
@@ -53,19 +53,19 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
|
||||
private static final int URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA = 6;
|
||||
private static final int URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES = 7;
|
||||
|
||||
private static final int URI_SHARE_PICTURE = 8;
|
||||
private static final int URI_CSV = 8;
|
||||
|
||||
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
private static final String TRACKID_DELIMITER = "_";
|
||||
|
||||
static {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*/*", URI_GPX);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getPreferenceId() + "/*/*", URI_GPX);
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getPreferenceId() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.CSV.getPreferenceId() + "/*/*", URI_CSV);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
/**
|
||||
* @return An URI for one file containing all tracks.
|
||||
*/
|
||||
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String filename, @NonNull TrackFileFormat trackFileFormat) {
|
||||
if (trackIds.isEmpty()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
}
|
||||
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||
String mime = getTypeMime(uri);
|
||||
|
||||
Log.d(TAG, "Created uri " + uri + " with MIME " + mime);
|
||||
@@ -146,6 +146,9 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
|
||||
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||
|
||||
case URI_CSV:
|
||||
return TrackFileFormat.CSV;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
public class Distance {
|
||||
|
||||
public static Distance of(double distance_m) {
|
||||
@@ -14,6 +17,14 @@ public class Distance {
|
||||
return of(Float.parseFloat(distance_m));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Distance ofOrNull(Double distance_m) {
|
||||
if (distance_m == null) {
|
||||
return null;
|
||||
}
|
||||
return of(distance_m);
|
||||
}
|
||||
|
||||
public static Distance ofMile(double distance_mile) {
|
||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||
}
|
||||
@@ -34,11 +45,14 @@ public class Distance {
|
||||
return of(0.1 * distance_dm);
|
||||
}
|
||||
|
||||
public static Distance one(boolean metricUnit) {
|
||||
if (metricUnit) {
|
||||
return Distance.ofKilometer(1);
|
||||
} else {
|
||||
return Distance.ofMile(1);
|
||||
public static Distance one(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return Distance.ofKilometer(1);
|
||||
case IMPERIAL:
|
||||
return Distance.ofMile(1);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,12 +114,26 @@ public class Distance {
|
||||
return toKM() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public double toKM_Miles(boolean metricUnit) {
|
||||
return metricUnit ? toKM() : toMI();
|
||||
public double toKM_Miles(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toKM();
|
||||
case IMPERIAL:
|
||||
return toMI();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public double toM_FT(boolean metricUnit) {
|
||||
return metricUnit ? toM() : toFT();
|
||||
public double toM_FT(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toM();
|
||||
case IMPERIAL:
|
||||
return toFT();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||