Compare commits

...

67 Commits

Author SHA1 Message Date
Dennis Guse 3ee7d4504c Release v3.9.2 2020-08-30 23:05:51 +02:00
Dennis Guse 579509aad8 Upgrade dependencies. 2020-08-30 19:01:27 +02:00
Peter Storch 24ff73ab06 Fix force close on stop button for older devices (#393)
* correct thread handling in ButtonDelay

Discovered in #390 for devices with API Level 25. Not fixing the whole issue, but a first step.

* fix #390 crash when stopping for older devices

The VectorDrawable ic_button_stop.xml referenced from withing the AnimatedVectorDrawable ic_button_stop_anim.xml was a problem for older devices. Now the VectorDrawable is expanded (duplicated) inside the AnimatedVectorDrawable. Now it is working. Even with the animation.
2020-08-27 08:11:10 +02:00
Dennis Guse 60d5e7e515 Reset recordingTrackId on application Startup.
This is necessary if the TrackRecordingService crashed.
2020-08-23 17:36:30 +02:00
Dennis Guse c610758ae1 Move CursorLoader to ContentProviderUtils. 2020-08-23 00:09:02 +02:00
Dennis Guse 17d2755844 Bugfix: MarkerDetailActivity: compare waypointId properly using equals.
Introduced in bcbaf4985a
2020-08-23 00:08:58 +02:00
Dennis Guse 0ab14e0f3d Implement Track.Id and Waypoint.Id (with tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse bcbaf4985a Implement Track.Id and Waypoint.Id (without tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse b2e8713db0 Desugar: removed manual date parsing on API26. 2020-08-21 07:01:16 +02:00
Dennis Guse af60e1fdfb Enable Google's desugar plugin (enables stream/map etc.).
Fixes  #347.
2020-08-20 18:31:47 +02:00
Román 75043325ff Fix comment. 2020-08-19 22:04:40 +02:00
Dennis Guse 337e5b9e4d Release v3.9.1 2020-08-19 18:13:10 +02:00
Román 1ff986f1e6 Gps Status: bugfixes.
Bugfix 1: race condition issue fixed with a flag.
Bugfix 2: location time issue fixed setting the time every time a location is received.
2020-08-19 14:02:47 +02:00
thrillfall 6412bb9fcd remove useless parameter 2020-08-18 16:37:44 +02:00
thrillfall 8425997c29 add overload for exporter that takes single track 2020-08-18 16:37:44 +02:00
thrillfall f4b585b830 do not pass complete track data to exporter factory method just to knwo if there is more then one track to export 2020-08-18 16:37:44 +02:00
thrillfall 47c259fa9a move tracks array from class state to method argument 2020-08-18 16:37:44 +02:00
thrillfall 80208e3188 move condition for writing multitrack begin and end outside of methods 2020-08-18 16:37:44 +02:00
thrillfall 2655d3e773 fix decorator TrackExporter interface 2020-08-18 16:37:44 +02:00
Dennis Guse ca8814c478 Release v3.9.0 2020-08-16 20:37:38 +02:00
Dennis Guse 6a397922d2 Cleanup using IntelliJ's analyze code. 2020-08-16 20:13:09 +02:00
Dennis Guse ee062f8a38 Cleanup. 2020-08-16 20:13:09 +02:00
Dennis Guse c8bdf8e641 Wheel circumference is now a textfield accepting input as mm.
Fixes #278.
2020-08-16 20:12:20 +02:00
Dennis Guse 0b2bd6899f Pull request template: add note about adding new libraries. 2020-08-16 12:49:50 +02:00
Román 32d811b46e Bugfix: NPE fixed because listeners/client were null in some cases.
TrackRecordingService: before destroying it sends GPS Status NONE to all listeners.
Cleanup Logs used for debugging.
2020-08-16 07:27:44 +02:00
Román 80a8017a18 GPS Status: animation icon for ready but not fixed status (GPS bad signal). 2020-08-16 07:27:44 +02:00
Román 2b90bfd054 ServiceUtils deleted because it doesn't need anymore.
It uses gps status received from service.
2020-08-16 07:27:44 +02:00
Román 8cb6d63abe Cleanup and rename. 2020-08-16 07:27:44 +02:00
Román 9837680540 GPS Status: TrackListActivity uses the value of gps status from the service to select appropiate icon. 2020-08-16 07:27:44 +02:00
Román e2b741dd08 Added GPS Status to the service so activities can access to the GPS Status. 2020-08-16 07:27:44 +02:00
Peter Storch 18546fd3cc fix #241 stop button lock (#362)
* fix #241 stop button lock

Adds a delay and animation to the stop and pause buttons

- short vibration when starting to push the buttons
- long vibration when action was executed
- Toast to show "Hold to stop/pause recording"
2020-08-15 21:50:06 +02:00
Dennis Guse a87d2e5b09 Bugfix: prevent NPE.
Fixes #374.
2020-08-15 21:23:14 +02:00
Dennis Guse 3ae30e2468 Settings: do prevent import of already existing tracks by default. 2020-08-15 21:12:10 +02:00
Dennis Guse f821c8ba86 Fixed tests. 2020-08-15 21:12:10 +02:00
Dennis Guse 5c96774e79 ContentProvider can now lookup tracks via UUID. 2020-08-15 21:12:10 +02:00
Dennis Guse c26a5edc03 Make track import behavior on duplicate UUID configurable (default: import with a new UUID). 2020-08-15 21:12:10 +02:00
Dennis Guse f8afa9dbad Import/export track UUID (necessary for synchronization).
If a track with a UUID is presented in OpenTracks, it cannot be imported again (enforced by database constraint).
Fixes #307.
2020-08-15 21:12:10 +02:00
Dennis Guse db1167ead5 Find track by UUID. 2020-08-15 21:12:10 +02:00
Dennis Guse 0008673ab0 Add class diagram for export/import code. 2020-08-15 21:12:10 +02:00
Dennis Guse 59d4bfa4ac Export KML: report OpenTracks as generator. 2020-08-15 21:12:10 +02:00
Dennis Guse 04f146c641 Bugfix: formatting of pace was not formatted correctly (e.g., 4:60).
Fixes #370.
2020-08-15 20:19:46 +02:00
Dennis Guse ea97ff65df MarkerEditActivity: fixed NPE on orientation change. 2020-08-15 09:12:19 +02:00
Peter Storch 9827b3fc92 fix #371 mountain bike icon not shown 2020-08-12 20:53:58 +02:00
Peter Storch a35ed2e9b1 move debug resources into debug and cleanup build.gradle 2020-08-11 21:42:38 +02:00
Dennis Guse 26d2a861d7 Add missing bluetooth icon (SVG). 2020-08-10 16:31:38 +02:00
Dennis Guse 389ed9b362 Update dependencies. 2020-08-10 15:52:52 +02:00
Dennis Guse c5e5e862ad App launcher icon: moved arrow slightly too the left. 2020-08-10 15:52:11 +02:00
Dennis Guse b676fd6614 SearchEngineTest: do not use production database.
Partly fixes #143.
2020-08-10 15:39:53 +02:00
Peter Storch 0194efafbc added more .idea files to gitignore 2020-08-08 12:41:50 +02:00
Peter Storch 9ba1357dbb added new activity types (#360)
* added new activity types

- escooter
- inline_skating
- motorbike
- skate_boarding
- skiing
- snowboarding
- climbing
- sailing
- mtb
2020-08-08 12:38:51 +02:00
Dennis Guse 64b66a0c57 Github sponsor button points to OpenTracks-team (liberapay). 2020-08-07 16:15:36 +02:00
Dennis Guse 7787dae9f6 Bluetooth preference use vector icon. 2020-08-06 12:34:01 +02:00
Dennis Guse d0ca06f964 Switch Liberapay to separate OpenTracks-team. 2020-08-05 17:19:01 +02:00
Dennis Guse f7d1a53622 File open intent-filters now expose host (required by lint). 2020-08-05 12:49:30 +02:00
Dennis Guse 5f13d027af CI: CodeQL use custom build command. 2020-08-04 22:58:57 +02:00
Dennis Guse cf45a8079c Simplified getting the BluetoothAdapter. 2020-08-04 22:56:35 +02:00
Dennis Guse 9e553cc900 CI: enable CodeQL 2020-08-04 22:49:14 +02:00
transifex-integration[bot] 1f3466e4b2 Translate /src/main/res/values/strings.xml in eu
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'eu' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:49:12 +02:00
transifex-integration[bot] c813934bcb Translate /src/main/res/values/strings.xml in cs
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'cs' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:49 +02:00
transifex-integration[bot] 480202853d Translate /src/main/res/values/strings.xml in da
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'da' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:29 +02:00
transifex-integration[bot] 4c58bd81bb Translate /src/main/res/values/strings.xml in nl
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'nl' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:12 +02:00
transifex-integration[bot] 9f9b6966a8 Translate /src/main/res/values/strings.xml in et
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'et' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:47:41 +02:00
transifex-integration[bot] 52c97b63d5 Translate /src/main/res/values/strings.xml in fi
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'fi' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:47:24 +02:00
transifex-integration[bot] eee18ec7a4 Translate /src/main/res/values/strings.xml in fr
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'fr' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:53 +02:00
transifex-integration[bot] 4d444046de Translate /src/main/res/values/strings.xml in gl
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'gl' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:34 +02:00
transifex-integration[bot] 4d5e31b231 Translate /src/main/res/values/strings.xml in de
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'de' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:10 +02:00
transifex-integration[bot] 8544f7d20a Translate /src/main/res/values/strings.xml in es
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'es' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:45:16 +02:00
153 changed files with 8043 additions and 1570 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms
liberapay: dennis.guse
liberapay: OpenTracks
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+4 -1
View File
@@ -7,4 +7,7 @@ A clear and concise description of what the pull request changes/adds.
(If available): The link to the issue that this pull request solves.
**License agreement**
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
**Note: new dependencies/libraries**
Please refrain from introducing new libraries without consulting the team.
+34
View File
@@ -0,0 +1,34 @@
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: java
- run: |
./gradlew build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+4 -1
View File
@@ -66,6 +66,9 @@ captures/
.idea/sqlDataSources.xml
.idea/vcs.xml
.idea/uiDesigner.xml
.idea/jarRepositories.xml
.idea/markdown-navigator-enh.xml
.idea/markdown-navigator.xml
# Keystore files
*.jks
@@ -76,4 +79,4 @@ captures/
# Fastlane
Appfile
releasePlayStore/*
Gemfile.lock
Gemfile.lock
+1 -1
View File
@@ -21,7 +21,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</a>
</td>
<td>
<a href="https://liberapay.com/dennis.guse/donate">
<a href="https://liberapay.com/OpenTracks/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
</a>
</td>
+14 -19
View File
@@ -37,6 +37,8 @@ def getVersionName = { ->
android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@@ -45,13 +47,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [
applicationLogo: "@mipmap/ic_launcher",
applicationName: "@string/app_name"
]
versionCode 3474
versionName "v3.8.1"
versionCode 3541
versionName "v3.9.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -67,10 +64,6 @@ android {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
manifestPlaceholders = [
applicationLogo: "@mipmap/ic_launcher_debug",
applicationName: "OpenTracks (Debug)"
]
}
release {
@@ -89,15 +82,17 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'org.mockito:mockito-android:3.4.4'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'org.mockito:mockito-android:3.4.6'
}
+29
View File
@@ -0,0 +1,29 @@
@startuml
interface TrackExporter {
}
class KmzTrackExporter implements TrackExporter
KmzTrackExporter o-- FileTrackExporter
class FileTrackExporter implements TrackExporter {
/' One track only '/
}
FileTrackExporter o-- TrackWriter
interface TrackWriter {
}
class GpxTrackWriter implements TrackWriter
class KmlTrackWriter implements TrackWriter
interface TrackImporter {
}
class KmzTrackImporter implements TrackImporter {
}
KmzTrackImporter o-- KmlFileTrackImporter
class AbstractFileTrackImporter implements TrackImporter {
}
class GpxFileTrackImporter extends AbstractFileTrackImporter {
}
class KmlFileTrackImporter extends AbstractFileTrackImporter {
/' Supports multiple tracks via folders '/
}
@enduml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
<xs:element name="trackuuid"
type="xs:string" />
</xs:schema>
+474
View File
@@ -0,0 +1,474 @@
<?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"
sodipodi:docname="ic_activity_climbing.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="0"
inkscape:window-y="64"
inkscape:window-x="219"
inkscape:window-height="880"
inkscape:window-width="1500"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="12.271037"
inkscape:cx="7.0404004"
inkscape:zoom="22.390336"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
id="path920"
d="M 6.3144531 -0.15234375 L 5.6464844 0.34179688 L 4.8945312 2.8847656 L 3.7050781 3.2324219 L 3.0292969 4.9121094 L 1.6992188 5.1113281 L 1.5371094 5.4570312 L -0.2265625 5.8183594 L -0.17382812 6.0761719 L 1.7207031 5.6914062 L 1.8789062 5.3515625 L 3.21875 5.1503906 L 3.9023438 3.4492188 L 5.1074219 3.0976562 L 5.875 0.50195312 L 6.4726562 0.060546875 L 6.3144531 -0.15234375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<g
transform="matrix(1.226984,0,0,1.226984,-0.81596813,-1.2462535)"
id="g924">
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.168524;stroke-opacity:1"
id="path17"
d="M 2.4564013,2.0252139 C 2.6387748,1.9919855 2.7608027,1.8155843 2.7275743,1.6332107 2.6943459,1.4508371 2.5179443,1.3288092 2.3355708,1.3620376 2.1531971,1.395266 2.0311693,1.5716675 2.0643978,1.7540412 2.0976262,1.9364148 2.2740276,2.0584424 2.4564013,2.0252139 Z M 2.0390821,4.4361706 2.2561417,3.8057362 2.6726641,3.9140028 3.0571092,4.4259571 3.3404272,4.1900271 2.8859586,3.5506757 2.4962919,3.3764255 2.6074394,2.8541847 C 2.8682831,3.0636061 3.230079,3.168985 3.5948261,3.1025284 L 3.5344109,2.7709399 C 3.219402,2.8283344 2.9239238,2.7108724 2.7489979,2.5029267 L 2.5348714,2.2678637 C 2.4504294,2.1804703 2.3388697,2.1322773 2.2228138,2.1534226 c -0.049738,0.00906 -0.079877,0.031683 -0.1296145,0.040745 L 1.9734996,2.8024787 1.8537998,3.4107898 1.689464,4.211545 1.0707466,5.2436753 1.3712806,5.4904898 Z"
sodipodi:nodetypes="sssssccccccccccccsccccccc" />
<path
id="rect915"
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.248933;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers"
transform="matrix(0.99300752,0.11805114,-0.2203786,0.97541441,0,0)"
d="m 2.276527,2.111288 v 0 0.8237278 -0.08059 z"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+496
View File
@@ -0,0 +1,496 @@
<?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"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_escooter.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="13.383174"
inkscape:cy="12.569634"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1444"
inkscape:window-height="780"
inkscape:window-x="245"
inkscape:window-y="142"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3168"
style="stroke:#909090;stroke-opacity:1">
<path
id="path901"
d="M 2.3632812 1.984375 A 0.1324995 0.1324995 0 0 0 2.25 2.1347656 A 0.1324995 0.1324995 0 0 0 2.3984375 2.2480469 C 2.6424435 2.2158044 2.9061719 2.3043649 3.0820312 2.4765625 C 3.2048822 2.5968554 3.2894144 2.7590671 3.3085938 2.9257812 C 3.3213476 3.0366427 3.3054638 3.1596364 3.2929688 3.2949219 C 3.2804368 3.4306071 3.2701901 3.583197 3.3105469 3.7382812 C 3.3438455 3.8662421 3.4102315 3.9828889 3.5019531 4.078125 C 3.5936748 4.1733611 3.7093185 4.2451189 3.8359375 4.2832031 A 0.1324995 0.1324995 0 0 0 4.0019531 4.1933594 A 0.1324995 0.1324995 0 0 0 3.9121094 4.0292969 C 3.8297238 4.0045171 3.7530387 3.9564973 3.6933594 3.8945312 C 3.63368 3.8325652 3.5900254 3.7531805 3.5683594 3.6699219 C 3.5418024 3.5678678 3.5466893 3.4492033 3.5585938 3.3203125 C 3.5705351 3.191022 3.5879256 3.0476308 3.5703125 2.8945312 C 3.5435298 2.6617257 3.4320694 2.4481756 3.2675781 2.2871094 C 3.0309201 2.0553789 2.691646 1.9409856 2.3632812 1.984375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
transform="scale(0.26458333)"
id="path905"
d="M 6 5.5 A 0.50083507 0.50083507 0 0 0 5.5 6 L 5.5 10 A 0.50083507 0.50083507 0 0 0 6 10.5 L 7 10.5 C 8.3752137 10.5 9.5000001 9.3752139 9.5 8 C 9.4999999 6.6247863 8.3752137 5.5000001 7 5.5 L 6 5.5 z M 6.5 6.5 L 7 6.5 C 7.8339253 6.5000001 8.4999999 7.1660747 8.5 8 C 8.5000001 8.8339251 7.8339253 9.4999999 7 9.5 L 6.5 9.5 L 6.5 6.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path909"
d="M 1.0585938 1.71875 A 0.1324995 0.1324995 0 0 0 0.92578125 1.8515625 A 0.1324995 0.1324995 0 0 0 1.0585938 1.984375 L 1.5878906 1.984375 A 0.1324995 0.1324995 0 0 0 1.7207031 1.8515625 A 0.1324995 0.1324995 0 0 0 1.5878906 1.71875 L 1.0585938 1.71875 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path913"
d="M 1.0585938 2.2480469 A 0.1324995 0.1324995 0 0 0 0.92578125 2.3808594 A 0.1324995 0.1324995 0 0 0 1.0585938 2.5136719 L 1.5878906 2.5136719 A 0.1324995 0.1324995 0 0 0 1.7207031 2.3808594 A 0.1324995 0.1324995 0 0 0 1.5878906 2.2480469 L 1.0585938 2.2480469 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
</g>
<path
id="path924"
style="opacity:1;fill:#fdbc4b;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke"
d="m -1.3252903,1.5032615 -0.4100651,0.6834418 a 0.12845631,0.12845631 60.481872 0 0 0.1101504,0.1945466 l 0.2495783,-10e-8 -0.1100188,0.5495576 a 0.08982544,0.08982544 21.142221 0 0 0.1651025,0.063848 L -0.91047797,2.3112131 A 0.12845634,0.12845634 60.481877 0 0 -1.0206284,2.1166665 H -1.2702067 L -1.1601879,1.567109 a 0.08982541,0.08982541 21.142225 0 0 -0.1651024,-0.063848 z"
sodipodi:nodetypes="ccccccc"
inkscape:original-d="m -1.0583333,1.0583332 -0.79375,1.3229167 0.4764566,-10e-8 L -1.5875,3.4395832 -0.79375003,2.1166665 H -1.2702067 Z"
inkscape:path-effect="#path-effect950" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
transform="scale(0.26458333)"
id="path887"
d="M 13.994141 4.5 A 0.49999949 0.49999949 0 0 0 13.552734 4.7773438 A 0.49999949 0.49999949 0 0 0 13.777344 5.4472656 L 14.908203 6.0136719 C 15.328243 6.2241517 15.636226 6.6066586 15.75 7.0625 L 17.390625 13.623047 C 15.381402 13.909129 13.837817 15.475476 13.601562 17.5 L 8.3984375 17.5 C 8.1378934 15.267332 6.3043435 13.50201 4.0039062 13.5 C 3.982495 13.499838 3.9608645 13.499856 3.9394531 13.5 L 2 13.5 A 0.49999949 0.49999949 0 0 0 1.5 14 A 0.49999949 0.49999949 0 0 0 2 14.5 L 3.9394531 14.5 A 0.50004949 0.50004949 0 0 0 3.9433594 14.5 C 3.9610145 14.499866 3.9784387 14.499866 3.9960938 14.5 A 0.50004949 0.50004949 0 0 0 4 14.5 C 5.9389193 14.5 7.5000003 16.061081 7.5 18 A 0.50004949 0.50004949 0 0 0 8 18.5 L 14 18.5 A 0.50004949 0.50004949 0 0 0 14.5 18 C 14.5 16.061081 16.061081 14.5 18 14.5 A 0.50004949 0.50004949 0 0 0 18.484375 13.878906 L 16.71875 6.8203125 C 16.534472 6.0819921 16.037756 5.460052 15.357422 5.1191406 A 0.50004949 0.50004949 0 0 0 15.355469 5.1191406 L 14.222656 4.5527344 A 0.49999949 0.49999949 0 0 0 13.994141 4.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157481;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path970"
d="M 4.7617188 4.1367188 C 4.4265639 4.1367188 4.1523438 4.4109389 4.1523438 4.7460938 C 4.1523438 5.0812486 4.4265639 5.3554687 4.7617188 5.3554688 C 5.0968736 5.3554687 5.3710937 5.0812486 5.3710938 4.7460938 C 5.3710937 4.4109389 5.0968736 4.1367188 4.7617188 4.1367188 z M 4.7617188 4.3378906 C 4.9879388 4.3378906 5.1679687 4.5198737 5.1679688 4.7460938 C 5.1679687 4.9723138 4.9879388 5.1542969 4.7617188 5.1542969 C 4.5354987 5.1542969 4.3535156 4.9723138 4.3535156 4.7460938 C 4.3535156 4.5198737 4.5354987 4.3378906 4.7617188 4.3378906 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.201567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:0.997" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.201567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 1.0221536,4.1367188 c -0.33515491,0 -0.60937501,0.2742201 -0.60937501,0.609375 0,0.3351548 0.2742201,0.6093749 0.60937501,0.609375 0.3351548,-10e-8 0.6093749,-0.2742202 0.609375,-0.609375 -1e-7,-0.3351549 -0.2742202,-0.609375 -0.609375,-0.609375 z m 0,0.2011718 c 0.22622,0 0.4062499,0.1819831 0.40625,0.4082032 -1e-7,0.2262201 -0.18003,0.4082031 -0.40625,0.4082031 -0.22622011,0 -0.40820321,-0.181983 -0.40820321,-0.4082031 0,-0.2262201 0.1819831,-0.4082032 0.40820321,-0.4082032 z"
id="path970-7" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

+515
View File
@@ -0,0 +1,515 @@
<?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"
sodipodi:docname="ic_activity_inlineskater.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="27"
inkscape:window-x="67"
inkscape:window-height="1025"
inkscape:window-width="1853"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="10.868962"
inkscape:cx="8.471442"
inkscape:zoom="22.390336"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
d="m 3.3722093,1.2527829 c 0.2415068,0 0.4391032,-0.1975962 0.4391032,-0.43910305 0,-0.24150691 -0.1975964,-0.43910339 -0.4391032,-0.43910339 -0.241507,0 -0.4391033,0.19759648 -0.4391033,0.43910339 0,0.24150685 0.1975963,0.43910305 0.4391033,0.43910305 z M 2.5818232,4.3045509 2.8013749,3.3385235 3.2624334,3.7776271 V 5.0949369 H 3.7015366 V 3.4482998 L 3.2404782,3.0091963 3.3722093,2.3505413 c 0.285417,0.3293275 0.7245203,0.5488792 1.207534,0.5488792 V 2.4603172 c -0.4171482,0 -0.7684308,-0.2195517 -0.944072,-0.5269241 L 3.4161195,1.5821105 C 3.3282991,1.4503796 3.1965678,1.3625589 3.0428817,1.3625589 c -0.065865,0 -0.1097755,0.021955 -0.1756413,0.021955 L 1.7255717,1.867528 V 2.8994208 H 2.1646753 V 2.1529448 L 2.5598682,1.9992587 2.2085854,3.7776271 1.1327827,3.5580754 1.0449623,3.9971789 Z"
id="path17"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.219552;stroke-opacity:1" />
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<g
transform="matrix(-0.30905888,1.6151387,-1.6151387,-0.30905888,9.3627856,1.8488419)"
id="g943">
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892"
cx="1.6512442"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="1.9359355"
id="path892-3"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-6"
cx="2.2206268"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="2.5053182"
id="path892-3-6-7"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
</g>
<g
id="g943-2"
transform="matrix(1.6443153,-0.0204346,0.0204346,1.6443153,-0.01361945,-2.6499566)">
<circle
r="0.12575205"
cy="4.9452085"
cx="1.6512442"
id="path892-7"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-0"
cx="1.9359355"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="2.2206268"
id="path892-3-6-9"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-6-7-3"
cx="2.5053182"
cy="4.9452085"
r="0.12575205" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+546
View File
@@ -0,0 +1,546 @@
<?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"
sodipodi:docname="ic_activity_motorbike.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect1386"
effect="spiro" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1382"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.99999977,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect1373"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect1369"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1357"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.093080149,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3277"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0,0,1 @ F,0,1,1,0,0,0,1 @ F,0,0,1,0,3.210527,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="27"
inkscape:window-x="67"
inkscape:window-height="1025"
inkscape:window-width="1853"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="12.219337"
inkscape:cx="11.776234"
inkscape:zoom="27.983802"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
id="path901"
d="M 1.8574219 3.3066406 A 0.1322915 0.1322915 0 0 0 1.7363281 3.375 L 1.0742188 4.5664062 A 0.13230472 0.13230472 0 0 0 1.2050781 4.7617188 L 2.3964844 4.6289062 A 0.1322915 0.1322915 0 0 0 2.5136719 4.4824219 A 0.1322915 0.1322915 0 0 0 2.3671875 4.3671875 L 1.4316406 4.4707031 L 1.9667969 3.5039062 A 0.1322915 0.1322915 0 0 0 1.9160156 3.3242188 A 0.1322915 0.1322915 0 0 0 1.8574219 3.3066406 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path905"
d="M 3.1757812 1.984375 A 0.1322915 0.1322915 0 0 0 3.0429688 2.1171875 A 0.1322915 0.1322915 0 0 0 3.1757812 2.2480469 L 3.6113281 2.2480469 L 4.5058594 4.6757812 A 0.1322915 0.1322915 0 0 0 4.6757812 4.7539062 A 0.1322915 0.1322915 0 0 0 4.7539062 4.5839844 L 3.828125 2.0703125 A 0.13230472 0.13230472 0 0 0 3.7050781 1.984375 L 3.1757812 1.984375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
transform="scale(0.26458333)"
id="path909"
d="M 4.9765625 11.5 A 0.50004949 0.50004949 0 0 0 4.5 12 L 4.5 13 A 0.50004949 0.50004949 0 0 0 5 13.5 L 8 13.5 A 0.50004949 0.50004949 0 0 0 8.4882812 13.064453 A 0.50004949 0.50004949 0 0 0 8.4921875 13.005859 A 0.50004949 0.50004949 0 0 0 8.2871094 12.595703 A 0.50004949 0.50004949 0 0 0 8.2382812 12.564453 A 0.50004949 0.50004949 0 0 0 8.0703125 12.505859 A 0.50004949 0.50004949 0 0 0 8.0117188 12.5 A 0.50004949 0.50004949 0 0 0 8 12.5 C 7.0833333 12.5 6.4130278 12.27292 5.9726562 12.052734 C 5.5322847 11.832549 5.3535156 11.646484 5.3535156 11.646484 A 0.50004949 0.50004949 0 0 0 4.9765625 11.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path913"
d="M 2.7773438 4.1015625 A 0.13249999 0.13249999 0 0 0 2.6464844 4.2324219 A 0.13249999 0.13249999 0 0 0 2.7773438 4.3652344 L 3.0429688 4.3652344 A 0.13249999 0.13249999 0 0 0 3.1757812 4.2324219 A 0.13249999 0.13249999 0 0 0 3.0429688 4.1015625 L 2.7773438 4.1015625 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<g
style="opacity:1"
transform="scale(0.26458333)"
id="g915">
<path
id="path917"
d="m 16.5,10.5 c -0.271556,0.0063 -0.500074,0.228371 -0.5,0.5 0,0.276144 0.223858,0.5 0.5,0.5 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1.0;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;" />
<path
id="path919"
d="M 16.488281 10 C 15.950439 10.012478 15.499849 10.447427 15.5 11 C 15.5 11.546787 15.953215 12 16.5 12 A 0.50083507 0.50083507 0 0 0 17 11.5 L 17 10.5 A 0.50083507 0.50083507 0 0 0 16.488281 10 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1.0;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;" />
</g>
<path
transform="scale(0.26458333)"
id="path923"
d="M 11 11.5 C 10.371809 11.5 9.9307721 11.946836 9.7089844 12.5 L 8 12.5 A 0.50004949 0.50004949 0 0 0 7.5234375 13.152344 L 9.1230469 18.152344 A 0.50004949 0.50004949 0 0 0 9.5996094 18.5 L 13 18.5 A 0.50004949 0.50004949 0 0 0 13.5 18 C 13.5 17.777537 13.499839 17.669786 13.5 17.5 C 13.500093 15.822204 14.542706 14.325749 16.117188 13.746094 A 0.50004949 0.50004949 0 0 0 16.414062 13.103516 L 15.943359 11.826172 A 0.50004949 0.50004949 0 0 0 15.474609 11.5 L 11 11.5 z M 11 12.5 L 15.126953 12.5 L 15.357422 13.128906 C 13.674401 13.966365 12.500106 15.589879 12.5 17.5 L 9.9648438 17.5 L 8.6855469 13.5 L 10 13.5 A 0.50004949 0.50004949 0 0 0 10.5 13 C 10.5 12.717935 10.717935 12.5 11 12.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:0.994135" />
<path
id="path927"
d="M 2.7773438 3.703125 A 0.13249999 0.13249999 0 0 0 2.6464844 3.8359375 A 0.13249999 0.13249999 0 0 0 2.7773438 3.96875 L 3.0429688 3.96875 A 0.13249999 0.13249999 0 0 0 3.1757812 3.8359375 A 0.13249999 0.13249999 0 0 0 3.0429688 3.703125 L 2.7773438 3.703125 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.261354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 4.6258209,3.8250746 c -0.4345653,0 -0.7901218,0.3555565 -0.7901218,0.7901218 0,0.4345652 0.3555565,0.7901217 0.7901218,0.7901218 0.4345652,-1e-7 0.7901217,-0.3555566 0.7901217,-0.7901218 0,-0.4345653 -0.3555565,-0.7901218 -0.7901217,-0.7901218 z m 0,0.2608413 c 0.2933192,0 0.5267476,0.2359612 0.5267479,0.5292805 0,0.2933193 -0.2334287,0.5292803 -0.5267479,0.5292803 -0.2933193,0 -0.5292805,-0.235961 -0.5292805,-0.5292803 0,-0.2933193 0.2359612,-0.5292805 0.5292805,-0.5292805 z"
id="path970-7-3" />
<path
id="path970-7-3-6"
d="m 1.2138007,3.8250746 c -0.43456528,0 -0.79012178,0.3555565 -0.79012178,0.7901218 0,0.4345652 0.3555565,0.7901217 0.79012178,0.7901218 0.4345652,-1e-7 0.7901217,-0.3555566 0.7901217,-0.7901218 0,-0.4345653 -0.3555565,-0.7901218 -0.7901217,-0.7901218 z m 0,0.2608413 c 0.2933192,0 0.5267476,0.2359612 0.5267479,0.5292805 0,0.2933193 -0.2334287,0.5292803 -0.5267479,0.5292803 -0.29331928,0 -0.52928048,-0.235961 -0.52928048,-0.5292803 0,-0.2933193 0.2359612,-0.5292805 0.52928048,-0.5292805 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.261354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

+464
View File
@@ -0,0 +1,464 @@
<?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"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_mtb.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="15.832358"
inkscape:cx="2.2323103"
inkscape:cy="8.8776466"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1596"
inkscape:window-height="917"
inkscape:window-x="123"
inkscape:window-y="27"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:0.196349"
id="path51"
d="M 1.8042087,1.0709424 C 1.9783861,0.94322723 2.0164009,0.69622363 1.8886857,0.52204593 1.7609704,0.34786823 1.5139667,0.30985363 1.3397894,0.43756873 1.1656114,0.56528423 1.1275967,0.81228773 1.2553124,0.98646553 1.3830273,1.1606434 1.6300308,1.198658 1.8042087,1.0709424 Z M 0.89628461,3.3192757 C 0.4529229,3.6443694 0.35999832,4.2481554 0.68509192,4.6915169 1.0101856,5.1348783 1.6139715,5.2278033 2.0573332,4.9027093 2.5006949,4.5776158 2.5936197,3.9738298 2.2685259,3.5304682 1.9434323,3.0871067 1.3396463,2.9941819 0.89628461,3.3192757 Z M 1.883176,4.6651945 C 1.5823234,4.8857933 1.143206,4.8182123 0.92260684,4.5173598 0.70200762,4.2165074 0.76958946,3.77739 1.0704419,3.5567908 1.3712945,3.3361914 1.8104113,3.4037735 2.0310104,3.7046258 2.2516097,4.0054783 2.1840285,4.4445951 1.883176,4.6651945 Z M 1.6405189,2.4083524 1.7418913,1.7496767 1.9614496,1.7834677 c 0.3567831,0.05491 0.7188509,-0.015794 1.051372,-0.2596139 L 2.7806119,1.2071669 C 2.5430963,1.3813245 2.2834214,1.4256443 2.0364186,1.3876293 L 1.5149667,1.3073761 C 1.3893532,1.3020906 1.2869605,1.3284748 1.1919542,1.3981379 1.0969485,1.4678006 1.0409979,1.5575218 1.0399363,1.6556909 L 0.92166874,2.4241458 c -0.0168959,0.1097797 0.009488,0.2121721 0.0675406,0.2913438 0.0696627,0.095006 0.15938086,0.150959 0.25755306,0.1520177 L 2.0785546,2.9393333 2.6590789,3.7310504 2.9757655,3.4988407 2.2559153,2.5171116 Z M 3.1130918,1.6938078 C 2.6697307,2.0189011 2.5768054,2.6226876 2.9018991,3.0660491 3.2269927,3.5094107 3.8307793,3.602335 4.2741404,3.2772417 4.7175021,2.952148 4.8104268,2.348362 4.4853331,1.9050004 4.1602394,1.4616389 3.5564535,1.368714 3.1130918,1.6938078 Z M 4.0999832,3.0397266 C 3.7991307,3.2603259 3.3600139,3.1927439 3.1394147,2.8918914 2.9188155,2.5910391 2.9863967,2.1519221 3.2872491,1.9313229 3.5881015,1.7107237 4.0272189,1.7783052 4.2478182,2.0791576 4.4684174,2.38001 4.4008356,2.8191274 4.0999832,3.0397266 Z" />
<path
sodipodi:nodetypes="ccccccccccccc"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 6.2355927,2.3458251 4.366485,3.4225311 3.0292969,4.9121094 2.0999371,5.0657395 1.5371094,5.4570312 -0.2265625,5.8183594 -0.17382812,6.0761719 1.7207031,5.6914062 2.2796245,5.3059739 3.21875,5.1503906 4.5637507,3.639328 6.4484834,2.5587157 Z"
id="path920" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+477
View File
@@ -0,0 +1,477 @@
<?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"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_sailing.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="13.656644"
inkscape:cy="9.1983306"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1596"
inkscape:window-height="917"
inkscape:window-x="123"
inkscape:window-y="27"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="ccccc"
id="path900"
d="M 0.56041665,4.4708333 1.1604167,5.4 h 3.5 l 0.6,-0.9291667 z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path902"
d="M 2.8024332,0.32721116 V 4.5 H 3.0184001 V 0.32721116 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244823px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path904"
d="M 2.6527855,1.0066933 2.6459669,4.192806 0.73848581,4.2035269 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.257218px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.281927px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 3.1616713,0.35463877 0.00682,3.82765233 1.9074808,0.01288 z"
id="path904-3" />
<path
id="path921"
d="M 2.8105515,0.32721116 V 0.75450025 L 2.0324942,0.57664397 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.286457px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

+482
View File
@@ -0,0 +1,482 @@
<?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"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_skateboarding.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
guidetolerance="7.7"
objecttolerance="7.7"
inkscape:lockguides="true"
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="11.195168"
inkscape:cx="12.030411"
inkscape:cy="9.6162221"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1853"
inkscape:window-height="1025"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
snapvisiblegridlinesonly="false"
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="true"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
inkscape:locked="true"
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:nodetypes="sssssccccccccccccsccccccccccc"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244651;stroke-opacity:1"
id="path17"
d="M 3.4497594,1.2723995 C 3.7188321,1.2675879 3.9350456,1.043501 3.9302338,0.7744294 3.9254222,0.50535723 3.7013352,0.28914379 3.4322626,0.29395542 3.1631898,0.29876705 2.9469764,0.5228543 2.9517881,0.7919262 2.9565981,1.0609979 3.1806867,1.2772111 3.4497594,1.2723995 Z M 3.0637352,4.3310097 2.8499094,3.4654121 3.5050565,3.7377246 4.0318851,5.1078311 4.4885872,4.9322216 3.8300515,3.2195885 3.1749046,2.9472761 3.3770914,2.4212785 C 3.7016475,2.7825092 4.1834322,3.0452834 4.7215778,3.0356596 L 4.7128291,2.5464372 C 4.2480673,2.5547482 3.9586663,2.3644029 3.7568534,2.0254461 L 3.5052429,1.6384419 C 3.4047741,1.4934249 3.2562572,1.3982048 3.0850292,1.4012668 c -0.073384,0.00131 -0.1218679,0.026649 -0.1952518,0.02796 L 1.627421,1.9901187 1.2471269,3.0265669 1.6538376,3.2423177 2.0691965,2.3071052 2.5595693,2.120264 2.1081061,3.2524111 2.4089713,4.1591882 1.3164273,4.8578585 1.9480117,4.971638 Z" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="cccccccccccccc"
d="M 0.70720657,4.618942 0.50519813,4.8871827 c -3e-8,0 0.1915452,0.1543669 0.29349555,0.2268814 0.0408697,0.022353 0.0297057,0.028139 0.10131117,0.035126 l 2.08762525,-0.0023 2.0876253,0.0023 c 0.071605,-0.00699 0.061055,-0.012773 0.1019258,-0.035126 C 5.2791315,5.04155 5.4700628,4.8871827 5.4700628,4.8871827 L 5.2680537,4.618942 5.0181529,4.8124246 3.1356059,4.811273 H 2.8396543 l -1.88193269,0.00115 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.304444px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path894-3" />
<circle
r="0.1149179"
cy="5.4143333"
cx="1.4282604"
id="path999"
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.264999;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.264999;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers"
id="path999-5"
cx="4.4387093"
cy="5.4143333"
r="0.1149179" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+469
View File
@@ -0,0 +1,469 @@
<?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"
sodipodi:docname="ic_activity_skiing.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="0"
inkscape:window-y="175"
inkscape:window-x="67"
inkscape:window-height="780"
inkscape:window-width="1444"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="11.008507"
inkscape:cx="12.634965"
inkscape:zoom="15.832358"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
sodipodi:nodetypes="ssssscccccccccccsacccccc"
d="M 4.5585535,1.8682464 C 4.730575,2.037758 5.0100109,2.0357042 5.1795224,1.8636824 5.3490338,1.6916608 5.3469796,1.4122249 5.1749586,1.2427137 5.0029369,1.0732022 4.7235009,1.075256 4.5539893,1.2472777 4.3844781,1.419299 4.3865322,1.6987351 4.5585535,1.8682464 Z M 2.5616473,2.8895379 2.9818066,3.7412695 2.1627798,4.156586 2.4890714,4.5159652 3.6644142,3.9783412 3.3324214,3.2071044 3.7880462,2.6501643 C 3.760192,3.0850707 3.9188577,3.5496566 4.2629009,3.8886795 L 4.5711034,3.575913 C 4.2739752,3.2831205 4.1778634,2.8801752 4.2684987,2.5379577 L 4.3586776,2.1336426 C 4.3885856,1.9781722 4.356396,1.8231572 4.2469278,1.7152862 4.2000132,1.6690561 4.0431309,1.5310148 3.9171527,1.4852939 3.6109432,1.3741621 2.9436171,1.4000918 2.9436171,1.4000918 L 1.9817647,1.9917143 2.2945313,2.2999171 3.0666299,1.8154812 3.4559921,1.8297725 Z"
id="path17"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.219552;stroke-opacity:1" />
<g
transform="matrix(0.38576851,0,0,0.38576851,152.97482,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
sodipodi:nodetypes="cccccccc"
id="path894-3"
d="m 0.42966147,3.4124371 3.27271563,1.98984 c 0.1037203,0.05566 0.091268,0.04209 0.159023,0.05977 0.1768227,0.031256 0.4686557,-0.030884 0.4686557,-0.030884 L 4.2534675,5.1832708 3.8763995,5.1892147 0.56600626,3.186415 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.369514px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1" />
<path
d="M 1.3650688,1.2665262 1.2347897,1.4356529 1.0541865,1.3107744 0.939761,1.4515535 1.1246616,1.5780728 0.99349664,1.748178 1.1114823,1.8390193 1.247937,1.6625738 4.3621692,3.7943987 4.471835,3.6701937 1.357598,1.5201785 1.4830545,1.3573675 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.238916px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path898" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

+470
View File
@@ -0,0 +1,470 @@
<?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"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_snowboarding.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
guidetolerance="7.7"
objecttolerance="7.7"
inkscape:lockguides="true"
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="3.9483707"
inkscape:cy="10.41996"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1853"
inkscape:window-height="1025"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
snapvisiblegridlinesonly="false"
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="true"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
inkscape:locked="true"
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:nodetypes="sssssccccccccccccsccccccccccc"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244651;stroke-opacity:1"
id="path17"
d="M 3.4497594,1.6623572 C 3.7188321,1.6575456 3.9350456,1.4334587 3.9302338,1.1643861 3.9254222,0.89531328 3.7013352,0.67909984 3.4322626,0.68391147 3.1631898,0.6887231 2.9469764,0.91281035 2.9517881,1.181883 2.9566019,1.4509556 3.1806867,1.6671688 3.4497594,1.6623572 Z M 3.0637352,4.7209674 2.8499094,3.8553698 3.5050565,4.1276823 4.0318851,5.4977888 4.4885872,5.3221793 3.8300515,3.6095462 3.1749046,3.3372338 3.3770914,2.8112362 C 3.7016475,3.1724669 4.1834322,3.4352411 4.7215778,3.4256173 L 4.7128291,2.9363949 C 4.2480673,2.9447059 3.9586663,2.7543606 3.7568534,2.4154038 L 3.5052429,2.0283996 C 3.4047741,1.8833826 3.2562572,1.7881625 3.0850292,1.7912245 3.0116457,1.7925394 2.9631613,1.8178734 2.8897774,1.819185 L 1.627421,2.3800764 1.2471269,3.4165246 1.6538376,3.6322754 2.0691965,2.6970629 2.5595693,2.5102217 2.1081061,3.6423688 2.4089713,4.5491459 1.3164273,5.2478162 1.9480117,5.3615957 Z" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="cccccccccccccc"
d="M 0.70720657,5.0088997 0.50519813,5.2771404 c -3e-8,0 0.1915452,0.1543669 0.29349555,0.2268814 0.0408697,0.022353 0.0297057,0.028139 0.10131117,0.035126 l 2.08762525,-0.0023 2.0876253,0.0023 c 0.071605,-0.00699 0.061055,-0.012773 0.1019258,-0.035126 C 5.2791315,5.4315077 5.4700628,5.2771404 5.4700628,5.2771404 L 5.2680537,5.0088997 5.0181529,5.2023823 3.1356059,5.2012307 H 2.8396543 l -1.88193269,0.00115 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.304444px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path894-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+29
View File
@@ -0,0 +1,29 @@
<?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="http://www.w3.org/2000/svg"
id="svg6"
version="1.1"
height="24px"
width="24px"
fill="black"
viewBox="0 0 24 24">
<metadata id="metadata12">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
id="dotSmall"
d="m 14.24,12.01 2.32,2.32 C 16.84,13.61 17,12.82 17,12 17,11.18 16.84,10.41 16.57,9.69 Z" />
<path
id="dotBig"
d="m 19.53,6.71 -1.26,1.26 c 0.63,1.21 0.98,2.57 0.98,4.02 0,1.45 -0.36,2.82 -0.98,4.02 l 1.2,1.2 C 20.44,15.67 21.01,13.85 21.01,11.9 21,10.01 20.46,8.23 19.53,6.71 Z" />
<path
d="M 12.88,16.29 11,18.17 V 14.41 Z M 11,5.83 12.88,7.71 11,9.59 Z M 15.71,7.71 10,2 H 9 V 9.59 L 4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41 V 22 h 1 L 15.71,16.29 11.41,12 Z"
id="path4" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,14 @@
v3.9.0: OpenTracks
Changes:
- Export tracks with their UUID
- Prevent import of already existing tracks by UUID (configurable; default yes)
- Pause/stop button now require long press
- GPS pre-start shows status of fix
- Added new activity icons
- Speed sensor: wheelsize now configurable in millimeter
Bugfixes:
- Fixed some rare crashes (NPE)
- Pace formatting corrected
- App icon in launcher repositioned
@@ -0,0 +1,4 @@
v3.9.1: OpenTracks
Bugfixes:
- GPS pre-start shows status of fix
@@ -0,0 +1,5 @@
v3.9.2: OpenTracks
Bugfixes:
- Fixed force close on API<25 (due to an animation)
- Reset recording state (on app startup) if crashed while recording
@@ -67,7 +67,7 @@ public class DescriptionGeneratorTest {
track.setTrackStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
@@ -103,7 +103,7 @@ public class DescriptionGeneratorTest {
track.setTrackStatistics(stats);
track.setCategory("hiking");
String expected = //"Created by"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
+ "Name: -<br>"
+ "Activity type: hiking<br>"
+ "Description: -<br>"
@@ -17,18 +17,17 @@
package de.dennisguse.opentracks.content;
import android.content.ContentUris;
import android.content.Context;
import android.location.Location;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.provider.ProviderTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
@@ -39,6 +38,8 @@ import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.CustomSQLiteOpenHelper;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
@@ -48,28 +49,32 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
*
* @author Rodrigo Damazio
*/
@RunWith(AndroidJUnit4.class)
public class SearchEngineTest {
private static final String TAG = SearchEngineTest.class.getSimpleName();
private static final Location HERE = new Location("gps");
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000; // After OLDEST_ALLOWED_TIMESTAMP
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000;
@Rule
public ProviderTestRule sqliteContentProviderRule = new ProviderTestRule.Builder(CustomContentProvider.class, ContentProviderUtils.AUTHORITY_PACKAGE).setPrefix(TAG).build();
private ContentProviderUtils providerUtils;
private SearchEngine engine;
private final Context context = ApplicationProvider.getApplicationContext();
@Before
public void setUp() {
providerUtils = new ContentProviderUtils(context);
providerUtils = new ContentProviderUtils(sqliteContentProviderRule.getResolver());
engine = new SearchEngine(providerUtils);
}
@After
public void tearDown() {
providerUtils.deleteAllTracks(context);
InstrumentationRegistry.getInstrumentation().getTargetContext().deleteDatabase(TAG + CustomSQLiteOpenHelper.DATABASE_NAME);
}
private long insertTrack(String title, String description, String category, long hoursAgo) {
private Track.Id insertTrack(String title, String description, String category, long hoursAgo) {
Track track = new Track();
track.setName(title);
track.setDescription(description);
@@ -83,18 +88,18 @@ public class SearchEngineTest {
}
Uri uri = providerUtils.insertTrack(track);
return ContentUris.parseId(uri);
return new Track.Id(ContentUris.parseId(uri));
}
private long insertTrack(String title, String description, String category) {
private Track.Id insertTrack(String title, String description, String category) {
return insertTrack(title, description, category, -1);
}
private long insertTrack(String title, long hoursAgo) {
private Track.Id insertTrack(String title, long hoursAgo) {
return insertTrack(title, "", "", hoursAgo);
}
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
private Waypoint.Id insertWaypoint(String title, String description, String category, double distance, long hoursAgo, Track.Id trackId) {
Location location = new Location(HERE);
location.setLatitude(location.getLatitude() + distance);
location.setLongitude(location.getLongitude() + distance);
@@ -108,22 +113,22 @@ public class SearchEngineTest {
waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
return new Waypoint.Id(ContentUris.parseId(uri));
}
private long insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, -1);
private Waypoint.Id insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, null);
}
private long insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, -1);
private Waypoint.Id insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, null);
}
private long insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, -1);
private Waypoint.Id insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, null);
}
private long insertWaypoint(String title, long hoursAgo, long trackId) {
private Waypoint.Id insertWaypoint(String title, long hoursAgo, Track.Id trackId) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
}
@@ -138,14 +143,14 @@ public class SearchEngineTest {
// - one which will match in title and description
// - one which will match in all fields
insertTrack("bb", "cc", "dd");
long descriptionMatchId = insertTrack("bb", "aa", "cc");
long categoryMatchId = insertTrack("bb", "cc", "aa");
long titleMatchId = insertTrack("aa", "bb", "cc");
long titleCategoryMatchId = insertTrack("aa", "bb", "ca");
long titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
long allMatchId = insertTrack("aa", "ba", "ca");
Track.Id descriptionMatchId = insertTrack("bb", "aa", "cc");
Track.Id categoryMatchId = insertTrack("bb", "cc", "aa");
Track.Id titleMatchId = insertTrack("aa", "bb", "cc");
Track.Id titleCategoryMatchId = insertTrack("aa", "bb", "ca");
Track.Id titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
Track.Id allMatchId = insertTrack("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
@@ -163,14 +168,14 @@ public class SearchEngineTest {
// - one which will match in title and description
// - one which will match in all fields
insertWaypoint("bb", "cc", "dd");
long descriptionMatchId = insertWaypoint("bb", "aa", "cc");
long categoryMatchId = insertWaypoint("bb", "cc", "aa");
long titleMatchId = insertWaypoint("aa", "bb", "cc");
long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
long allMatchId = insertWaypoint("aa", "ba", "ca");
Waypoint.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc");
Waypoint.Id categoryMatchId = insertWaypoint("bb", "cc", "aa");
Waypoint.Id titleMatchId = insertWaypoint("aa", "bb", "cc");
Waypoint.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
Waypoint.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
Waypoint.Id allMatchId = insertWaypoint("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
@@ -185,13 +190,13 @@ public class SearchEngineTest {
// - one waypoint which will match by title
// - one track which won't match
// - one track which will match by title
long descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
Waypoint.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
insertWaypoint("bb", "cc", "dd");
long titleWaypointId = insertWaypoint("aa", "bb", "cc");
Waypoint.Id titleWaypointId = insertWaypoint("aa", "bb", "cc");
insertTrack("bb", "cc", "dd");
long trackId = insertTrack("aa", "bb", "cc");
Track.Id trackId = insertTrack("aa", "bb", "cc");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
@@ -204,11 +209,11 @@ public class SearchEngineTest {
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertWaypoint("aa", 0.3);
long nearId = insertWaypoint("ab", 0.1);
long farId = insertWaypoint("ac", 0.2);
Waypoint.Id farFarAwayId = insertWaypoint("aa", 0.3);
Waypoint.Id nearId = insertWaypoint("ab", 0.1);
Waypoint.Id farId = insertWaypoint("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
SearchQuery query = new SearchQuery("a", HERE, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Distance order.
@@ -217,11 +222,11 @@ public class SearchEngineTest {
public void testSearchTrackRecent() {
// All results match text, but they're were recorded at different times.
long oldestId = insertTrack("aa", 3);
long recentId = insertTrack("ab", 1);
long oldId = insertTrack("ac", 2);
Track.Id oldestId = insertTrack("aa", 3);
Track.Id recentId = insertTrack("ab", 1);
Track.Id oldId = insertTrack("ac", 2);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
@@ -231,11 +236,11 @@ public class SearchEngineTest {
@Test
public void testSearchWaypointRecent() {
// All results match text, but they're were recorded at different times.
long oldestId = insertWaypoint("aa", 2);
long recentId = insertWaypoint("ab", 0);
long oldId = insertWaypoint("ac", 1);
Waypoint.Id oldestId = insertWaypoint("aa", 2);
Waypoint.Id recentId = insertWaypoint("ab", 0);
Waypoint.Id oldId = insertWaypoint("ac", 1);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
@@ -245,8 +250,8 @@ public class SearchEngineTest {
@Test
public void testSearchCurrentTrack() {
// All results match text, but one of them is the current track.
long currentId = insertTrack("ab", 1);
long otherId = insertTrack("aa", 1);
Track.Id currentId = insertTrack("ab", 1);
Track.Id otherId = insertTrack("aa", 1);
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
@@ -258,24 +263,29 @@ public class SearchEngineTest {
@Test
public void testSearchCurrentTrackWaypoint() {
// All results match text, but one of them is in the current track.
long otherId = insertWaypoint("aa", 1, 456);
long currentId = insertWaypoint("ab", 1, 123);
Waypoint.Id otherId = insertWaypoint("aa", 1, new Track.Id(456));
Waypoint.Id currentId = insertWaypoint("ab", 1, new Track.Id(123));
SearchQuery query = new SearchQuery("a", null, 123, NOW);
SearchQuery query = new SearchQuery("a", null, new Track.Id(123), NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Waypoint in current track should be promoted.
assertWaypointResults(results, currentId, otherId);
}
private void assertTrackResult(long trackId, ScoredResult result) {
private void assertTrackResult(Track.Id trackId, ScoredResult result) {
Assert.assertNotNull("Not a track", result.track);
Assert.assertNull("Ambiguous result", result.waypoint);
Assert.assertEquals(trackId, result.track.getId());
}
private void assertTrackResults(List<ScoredResult> results, long... trackIds) {
String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results;
private void assertTrackResults(List<ScoredResult> results, Track.Id... trackIds) {
long[] longTrackIds = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
longTrackIds[i] = trackIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longTrackIds) + "; results=" + results;
Assert.assertEquals(results.size(), trackIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
@@ -285,14 +295,19 @@ public class SearchEngineTest {
}
}
private void assertWaypointResult(long waypointId, ScoredResult result) {
private void assertWaypointResult(Waypoint.Id waypointId, ScoredResult result) {
Assert.assertNotNull("Not a waypoint", result.waypoint);
Assert.assertNull("Ambiguous result", result.track);
Assert.assertEquals(waypointId, result.waypoint.getId());
}
private void assertWaypointResults(List<ScoredResult> results, long... waypointIds) {
String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results;
private void assertWaypointResults(List<ScoredResult> results, Waypoint.Id... waypointIds) {
long[] longWaypointIds = new long[waypointIds.length];
for (int i = 0; i < waypointIds.length; i++) {
longWaypointIds[i] = waypointIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longWaypointIds) + "; results=" + results;
Assert.assertEquals(results.size(), waypointIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
@@ -20,10 +20,10 @@ public class TestDataUtil {
/**
* Create a track without any trackPoints.
*/
public static Track createTrack(long trackId) {
public static Track createTrack(Track.Id trackId) {
Track track = new Track();
track.setId(trackId);
track.setName("Test: " + trackId);
track.setName("Test: " + trackId.getId());
return track;
}
@@ -34,7 +34,7 @@ public class TestDataUtil {
* @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track
*/
public static Pair<Track, TrackPoint[]> createTrack(long trackId, int numPoints) {
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
@@ -45,7 +45,7 @@ public class TestDataUtil {
return new Pair<>(track, trackPoints);
}
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, long trackId, int numPoints) {
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
@@ -93,12 +93,11 @@ public class TestDataUtil {
* @param location The location.
* @return the Waypoint created.
*/
public static Waypoint createWaypointWithPhoto(Context context, long trackId, Location location) throws IOException {
String photoUrl = "";
public static Waypoint createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
photoUrl = photoUri.toString();
String photoUrl = photoUri.toString();
return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
}
@@ -22,7 +22,6 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -30,6 +29,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests {@link CustomContentProvider}.
*
@@ -51,7 +53,7 @@ public class CustomContentProviderTest {
*/
@Test
public void testOnCreate() {
Assert.assertTrue(customContentProvider.onCreate(context));
assertTrue(customContentProvider.onCreate(context));
}
/**
@@ -59,13 +61,13 @@ public class CustomContentProviderTest {
*/
@Test
public void testGetType() {
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
Assert.assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
Assert.assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
}
}
@@ -83,39 +83,39 @@ public class CustomContentProviderUtilsTest {
@Test
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false);
testIterator(new Track.Id(1), 0, 1, false);
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false);
testIterator(2, 50, 50, false);
testIterator(new Track.Id(1), 50, 100, false);
testIterator(new Track.Id(2), 50, 50, false);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true);
testIterator(2, 50, 50, true);
testIterator(new Track.Id(1), 50, 100, true);
testIterator(new Track.Id(2), 50, 50, true);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false);
testIterator(2, 50, 25, false);
testIterator(new Track.Id(1), 50, 11, false);
testIterator(new Track.Id(2), 50, 25, false);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true);
testIterator(2, 50, 25, true);
testIterator(new Track.Id(1), 50, 11, true);
testIterator(new Track.Id(2), 50, 25, true);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false);
testIterator(new Track.Id(1), 20000, 2000, false);
}
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) {
private void testIterator(Track.Id trackId, int numPoints, int batchSize, boolean descending) {
long lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints);
@@ -132,10 +132,10 @@ public class CustomContentProviderUtilsTest {
}
}
private long initializeTrack(long id, int numPoints) {
private long initializeTrack(Track.Id id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setName("Test: " + id.getId());
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
@@ -173,13 +173,13 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testCreateTrack() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
int columnIndex = 1;
// Id
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId.getId());
//Uuid
columnIndex++;
@@ -189,7 +189,7 @@ public class CustomContentProviderUtilsTest {
// Name
columnIndex++;
String name = NAME_PREFIX + trackId;
String name = NAME_PREFIX + trackId.getId();
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getString(columnIndex)).thenReturn(name);
@@ -205,7 +205,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testDeleteAllTracks() {
// Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -235,7 +235,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
// Insert track, points and waypoint with photo at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -268,20 +268,23 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/
@Test
public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track.
long random = System.currentTimeMillis();
Track.Id trackId1 = new Track.Id(random);
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
long trackId = System.currentTimeMillis();
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId + 1));
waypoint.setTrackId(trackId);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId2));
waypoint.setTrackId(trackId1);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
@@ -292,7 +295,7 @@ public class CustomContentProviderUtilsTest {
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
// Delete one track.
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
@@ -303,26 +306,30 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/
@Test
public void testDeleteTrack_withWaypointPhoto() throws IOException {
// Insert three tracks.
long trackId = System.currentTimeMillis();
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
long random = System.currentTimeMillis();
Track.Id trackId1 = new Track.Id(random);
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
// Insert a waypoint in tracks trackId and trackId + 1.
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId);
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint1.getLocation());
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId1, trackPoint1.getLocation());
contentProviderUtils.insertWaypoint(waypoint1);
File dir1 = FileUtils.getPhotoDir(context, trackId);
File dir1 = FileUtils.getPhotoDir(context, trackId1);
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId + 1);
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId + 1, trackPoint2.getLocation());
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId2, trackPoint2.getLocation());
contentProviderUtils.insertWaypoint(waypoint2);
File dir2 = FileUtils.getPhotoDir(context, trackId + 1);
File dir2 = FileUtils.getPhotoDir(context, trackId2);
// Check.
ContentResolver contentResolver = context.getContentResolver();
@@ -340,7 +347,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(1, dir2.list().length);
Assert.assertTrue(dir2.exists());
// Delete one track.
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
@@ -353,17 +360,17 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getAllTracks()}
* Tests the method {@link ContentProviderUtils#getTracks()}
*/
@Test
public void testGetAllTracks() {
// given
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
long trackId = System.currentTimeMillis();
int initialTrackNumber = contentProviderUtils.getTracks().size();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when
List<Track> allTracks = contentProviderUtils.getAllTracks();
List<Track> allTracks = contentProviderUtils.getTracks();
// then
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
@@ -375,28 +382,45 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testGetLastTrack() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
}
/**
* Tests the method {@link ContentProviderUtils#getTrack(long)}
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/
@Test
public void testGetTrack() {
long trackId = System.currentTimeMillis();
public void testGetTrack_by_id() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
}
/**
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/
@Test
public void testGetTrack_by_uuid() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
contentProviderUtils.insertTrack(track);
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(track.getUuid()));
}
/**
* Tests the method {@link ContentProviderUtils#updateTrack(Track)}
*/
@Test
public void testUpdateTrack() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
String nameOld = "name1";
String nameNew = "name2";
@@ -415,7 +439,7 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
// Bottom
@@ -441,10 +465,10 @@ public class CustomContentProviderUtilsTest {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
long waypointId = System.currentTimeMillis();
Waypoint.Id waypointId = new Waypoint.Id(System.currentTimeMillis());
waypoint.setId(waypointId);
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
Assert.assertEquals(waypointId.getId(), contentValues.get(WaypointsColumns._ID));
Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
}
@@ -478,19 +502,19 @@ public class CustomContentProviderUtilsTest {
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(id, waypoint.getId().getId());
Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId());
Assert.assertEquals(trackId, waypoint.getTrackId().getId());
}
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -503,7 +527,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Delete
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
@@ -513,12 +537,12 @@ public class CustomContentProviderUtilsTest {
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -530,7 +554,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Check waypoint has photo and it's in the external storage.
Assert.assertTrue(waypoint1.hasPhoto());
@@ -548,11 +572,11 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, long)} when there is more than one waypoint in the track.
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)} when there is more than one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Track track = TestDataUtil.createTrackAndInsert(trackId, 10);
@@ -575,12 +599,12 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId);
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId);
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
Waypoint.Id waypoint2Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2)));
// Delete
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
@@ -591,11 +615,11 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(Track.Id)}.
*/
@Test
public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -616,17 +640,17 @@ public class CustomContentProviderUtilsTest {
/**
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtils#getWaypoint(long)}.
* {@link ContentProviderUtils#getWaypoint(Waypoint.Id)}.
*/
@Test
public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
@@ -636,14 +660,14 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
// Update
waypoint = contentProviderUtils.getWaypoint(waypointId);
@@ -660,7 +684,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_withPhoto() throws IOException {
// tests after update waypoint with photo the photo remains in the storage.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -668,9 +692,9 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
@@ -696,7 +720,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -704,9 +728,9 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
@@ -731,7 +755,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert two waypoints with photos.
@@ -742,10 +766,10 @@ public class CustomContentProviderUtilsTest {
Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
otherWaypoint.setDescription(TEST_DESC);
otherWaypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
long otherWaypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(otherWaypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
contentProviderUtils.insertWaypoint(otherWaypoint);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(2, dir.list().length);
@@ -763,12 +787,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], Track.Id)}.
*/
@Test
public void testBulkInsertTrackPoint() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
@@ -824,12 +848,12 @@ public class CustomContentProviderUtilsTest {
/**
* Tests the method
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, long)}.
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, Track.Id)}.
*/
@Test
public void testInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
@@ -839,7 +863,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testInsertAndLoadTrackPoint() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
@@ -859,12 +883,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(Track.Id)}.
*/
@Test
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -872,12 +896,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in descending.
*/
@Test
public void testGetTrackPointCursor_desc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -894,12 +918,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in ascending.
*/
@Test
public void testGetTrackPointCursor_asc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -916,12 +940,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -945,12 +969,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -978,7 +1002,7 @@ public class CustomContentProviderUtilsTest {
/**
* Checks the value of a location.
*
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(long, int)}
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(Track.Id, int)}
* @param location the location to be checked
*/
private void checkLocation(int i, Location location) {
@@ -988,12 +1012,10 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
@Test
public void testFormatIdListForUri() {
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri(new long[]{}));
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new long[]{12}));
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new long[]{42, 43, 44}));
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri());
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new Track.Id(12)));
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new Track.Id(42), new Track.Id(43), new Track.Id(44)));
}
}
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.content.provider;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -9,7 +10,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -21,6 +22,10 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class CustomSQLiteOpenHelperTest {
@@ -88,6 +93,7 @@ public class CustomSQLiteOpenHelperTest {
}
@Before
@After
public void setUp() {
context.deleteDatabase(DATABASE_NAME);
}
@@ -95,13 +101,15 @@ public class CustomSQLiteOpenHelperTest {
@Test
public void onCreate() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
} catch (Exception e) {
fail();
}
}
@@ -127,18 +135,18 @@ public class CustomSQLiteOpenHelperTest {
}
// then - verify table structure
Assert.assertEquals(3, tableByUpgrade.size());
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size());
assertEquals(3, tableByUpgrade.size());
assertEquals(tableByUpgrade.size(), tablesByCreate.size());
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
// then - verify custom indices
Assert.assertEquals(3, indicesByCreate.size());
Assert.assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
assertEquals(3, indicesByCreate.size());
assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
}
@Test
@@ -155,12 +163,23 @@ public class CustomSQLiteOpenHelperTest {
}
// then - verify table structure
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
// then - verify custom indices
Assert.assertEquals(0, indicesByDowngrade.size());
assertEquals(0, indicesByDowngrade.size());
}
@Test
public void track_uuid_unique() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
fail("unique constraint not enforced");
} catch (SQLiteConstraintException e) {
assertTrue(e.getMessage().contains("UNIQUE constraint failed: tracks.uuid"));
}
}
private void createVersion23() {
@@ -6,6 +6,10 @@ import android.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
public class ShareContentProviderTest {
@@ -14,20 +18,25 @@ public class ShareContentProviderTest {
public void testCreateandParseURI_invalid() {
Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1");
Assert.assertArrayEquals(new long[]{}, ShareContentProvider.parseURI(uri));
Assert.assertEquals(0, ShareContentProvider.parseURI(uri).size());
}
@Test
public void testCreateandParseURI_valid() {
long[] trackIds = {1, 3, 5};
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
trackIds.add(new Track.Id(3));
trackIds.add(new Track.Id(5));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
Assert.assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
}
@Test
public void testCreateURIescapeFilename() {
long[] trackIds = {1};
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
@@ -253,7 +253,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point.getSpeed(), 0.01);
Assert.assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
}
/**
@@ -278,7 +278,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
Assert.assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
}
/**
@@ -303,7 +303,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point.getPace(), 0.01);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
}
/**
@@ -29,13 +29,13 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -64,11 +64,11 @@ public abstract class AbstractTestFileTrackImporter {
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
static final long TRACK_ID_0 = 1;
static final long TRACK_POINT_ID_0 = 1;
static final long TRACK_POINT_ID_1 = 2;
static final long TRACK_POINT_ID_3 = 4;
static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
private static final long WAYPOINT_ID_0 = 1;
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
@@ -76,7 +76,7 @@ public abstract class AbstractTestFileTrackImporter {
static {
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
TimeZone utc = SimpleTimeZone.getTimeZone(ZoneOffset.UTC);
DATE_FORMAT_0.setTimeZone(utc);
DATE_FORMAT_1.setTimeZone(utc);
}
@@ -96,11 +96,11 @@ public abstract class AbstractTestFileTrackImporter {
/**
* Expects the first track point to be added.
*
* @param trackPoint the trackPoint
* @param trackPoint the trackPoint
* @param trackId the track id
* @param trackPointId the track point id
*/
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
}
@@ -111,10 +111,10 @@ public abstract class AbstractTestFileTrackImporter {
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
*/
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, Track.Id trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI);
when(contentProviderUtils.insertWaypoint(any())).thenReturn(WAYPOINT_ID_O_URI);
if (lastTrack) {
// Return null to not add waypoints
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -28,9 +29,11 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
@@ -53,8 +56,8 @@ public class ExportImportTest {
private final List<Waypoint> waypoints = new ArrayList<>();
private final List<TrackPoint> trackPoints = new ArrayList<>();
private long importTrackId;
private final long trackId = System.currentTimeMillis();
private Track.Id importTrackId;
private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
@Before
public void setUp() {
@@ -103,16 +106,17 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
// then
@@ -123,6 +127,7 @@ public class ExportImportTest {
assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. waypoints
assertWaypoints();
@@ -137,16 +142,17 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L);
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
// then
@@ -165,6 +171,30 @@ public class ExportImportTest {
assertTrackpoints(true, true, true);
}
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
// then
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNull(importedTrack);
}
@LargeTest
@Test
public void kmz_only_track() {
@@ -199,12 +229,13 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
@@ -229,6 +260,31 @@ public class ExportImportTest {
assertTrackpoints(false, true, true);
}
@LargeTest
@Test
public void gpx_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track trackImported = contentProviderUtils.getTrack(importTrackId);
assertNull(trackImported);
}
private void assertWaypoints() {
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
@@ -1,217 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.io.file.importer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link GpxFileTrackImporter}.
*
* @author Steffen Horlacher
*/
@RunWith(MockitoJUnitRunner.class)
public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</trkseg></trk></gpx>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</trkseg><trkseg>"
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) + "</trkseg></trk></gpx>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_ELEVATION), "invalid");
private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "invalid");
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LONGITUDE), "invalid");
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
}
private static String getTrackPoint(int index, String time) {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String elevation = Double.toString(TRACK_ELEVATION + index);
StringBuilder buffer = new StringBuilder();
buffer.append("<trkpt lat=\"").append(latitude).append("\" lon=\"").append(longitude).append("\"><ele>").append(elevation).append("</ele>");
if (time != null) {
buffer.append("<time>").append(time).append("</time>");
}
buffer.append("</trkpt>");
return buffer.toString();
}
@Test
public void testOneTrackOneSegment() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPoint1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
Assert.assertEquals(TRACK_ID_0, trackId);
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
@Test
public void testOneTrackTwoSegmentsNoTime() {
// given
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
}
@Test
public void testInvalidXml() {
testInvalidGpx(INVALID_XML_GPX);
}
@Test
public void testInvalidLocation() {
testInvalidGpx(INVALID_LOCATION_GPX);
}
@Test
public void testInvalidTime() {
testInvalidGpx(INVALID_TIME_GPX);
}
@Test
public void testInvalidAltitude() {
testInvalidGpx(INVALID_ALTITUDE_GPX);
}
@Test
public void testInvalidLatitude() {
testInvalidGpx(INVALID_LATITUDE_GPX);
}
@Test
public void testInvalidLongitude() {
testInvalidGpx(INVALID_LONGITUDE_GPX);
}
private void testInvalidGpx(String xml) {
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
// For the following, use StubReturn since we don't care whether they are invoked or not.
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), anyLong())).thenReturn(1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
Assert.assertEquals(-1L, trackId);
}
}
@@ -1,133 +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.io.file.importer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
@RunWith(MockitoJUnitRunner.class)
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name><description><![CDATA[" + description
+ "]]></description>";
}
private static String getTrackPoint(int index, String time) {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String altitude = Double.toString(TRACK_ELEVATION + index);
return "<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>";
}
@Test
public void testOneTrackOneSegment() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPointl1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
}
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.IBinder;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,6 +34,7 @@ import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -69,10 +71,17 @@ public class TrackRecordingServiceTest {
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private final long trackId = Math.abs(new Random().nextLong());
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class);
@@ -125,7 +134,7 @@ public class TrackRecordingServiceTest {
@Test
public void testRecording_noTracks() throws Exception {
// given
List<Track> tracks = contentProviderUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getTracks();
Assert.assertTrue(tracks.isEmpty());
// when
@@ -136,7 +145,7 @@ public class TrackRecordingServiceTest {
// then
// Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
Assert.assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -150,7 +159,7 @@ public class TrackRecordingServiceTest {
// then
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
Assert.assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -234,7 +243,7 @@ public class TrackRecordingServiceTest {
// then
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
Assert.assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -245,13 +254,13 @@ public class TrackRecordingServiceTest {
service.startNewTrack();
Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
Track.Id trackId = service.getRecordingTrackId();
// when
long newTrackId = service.startNewTrack();
Track.Id newTrackId = service.startNewTrack();
// then
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
Assert.assertNull(newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
@@ -270,7 +279,7 @@ public class TrackRecordingServiceTest {
// then
Assert.assertFalse(PreferencesUtils.isRecording(context));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
Assert.assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -281,10 +290,10 @@ public class TrackRecordingServiceTest {
Assert.assertFalse(service.isRecording());
// when
long waypointId = service.insertWaypoint(null, null, null, null);
Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then
Assert.assertEquals(-1L, waypointId);
Assert.assertNull(waypointId);
}
@MediumTest
@@ -295,10 +304,10 @@ public class TrackRecordingServiceTest {
service.startNewTrack();
Assert.assertTrue(service.isRecording());
newTrackPoint(service);
long trackId = service.getRecordingTrackId();
Track.Id trackId = service.getRecordingTrackId();
// when
long waypointId = service.insertWaypoint(null, null, null, null);
Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then
Assert.assertNotEquals(-1L, waypointId);
@@ -313,15 +322,15 @@ public class TrackRecordingServiceTest {
}
private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0);
Assert.assertTrue(track.getId().isValid());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -89,7 +90,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -113,7 +114,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -155,7 +156,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_idle() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -181,7 +182,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -209,7 +210,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@@ -253,7 +254,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_segment() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -187,7 +187,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = contentProviderUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getTracks();
Assert.assertTrue(tracks.isEmpty());
fullRecordingSession();
}
@@ -197,14 +197,14 @@ public class TrackRecordingServiceTestLooper {
Assert.assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
Assert.assertTrue(id >= 0);
Track.Id trackId = service.startNewTrack();
Assert.assertTrue(trackId.isValid());
Assert.assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(id);
Track track = contentProviderUtils.getTrack(trackId);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(id, service.getRecordingTrackId());
Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis();
@@ -228,10 +228,10 @@ public class TrackRecordingServiceTestLooper {
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = contentProviderUtils.getTrack(id);
Assert.assertNull(service.getRecordingTrackId());
track = contentProviderUtils.getTrack(trackId);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(trackId, track.getId());
TrackStatistics trackStatistics = track.getTrackStatistics();
Assert.assertNotNull(trackStatistics);
Assert.assertTrue(trackStatistics.getStartTime_ms() > 0);
@@ -212,4 +212,16 @@ public class StringUtilsTest {
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
}
@Test
public void testGetSpeedParts() {
Assert.assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first);
Assert.assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first);
Assert.assertEquals("11.99", StringUtils.getSpeedParts(context, 3.33, true, true).first);
Assert.assertEquals("7.45", StringUtils.getSpeedParts(context, 3.33, false, true).first);
Assert.assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
Assert.assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
}
}
@@ -30,6 +30,7 @@ import java.util.Date;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
/**
* Tests {@link TrackNameUtils}.
@@ -39,7 +40,7 @@ import de.dennisguse.opentracks.R;
@RunWith(AndroidJUnit4.class)
public class TrackNameUtilsTest {
private static final long TRACK_ID = 1L;
private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final long START_TIME = 1288213406000L;
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -69,6 +70,6 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_number() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
Assert.assertEquals("Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
Assert.assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_debug_background" />
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_logo_color_108dp" />
</adaptive-icon>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_debug_background">#8F0000</color>
<color name="ic_launcher_background">#8F0000</color>
</resources>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 Google Inc.
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_button_stop_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/ic_button_stop" />
</selector>
<resources>
<string name="app_name">OpenTracks (Debug)</string>
</resources>
+6 -6
View File
@@ -43,6 +43,7 @@ limitations under the License.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -52,8 +53,8 @@ limitations under the License.
android:allowBackup="false"
android:hasFragileUserData="true"
android:hardwareAccelerated="true"
android:icon="${applicationLogo}"
android:label="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/ThemeCustom">
<activity android:name=".AboutActivity" />
@@ -75,33 +76,32 @@ limitations under the License.
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kmz" />
<data
android:scheme="content"
android:host="*"
android:pathPattern="*kmz" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kml+xml" />
<data
android:scheme="content"
android:host="*"
android:pathPattern="*kml" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx+xml" />
<data
android:scheme="content"
android:host="*"
android:pathPattern="*gpx" />
</intent-filter>
</activity>
<activity android:name=".MarkerDetailActivity">
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks;
import android.os.Bundle;
import android.view.Menu;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.Nullable;
@@ -30,7 +29,7 @@ public class AboutActivity extends AbstractActivity {
TextView textURL = findViewById(R.id.about_app_url);
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
}
protected int getLayoutResId() {
@@ -18,6 +18,10 @@ package de.dennisguse.opentracks;
import android.content.Intent;
import java.util.ArrayList;
import java.util.Arrays;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -55,17 +59,17 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
*
* @param trackIds the track ids
*/
protected void deleteTracks(long[] trackIds) {
protected void deleteTracks(Track.Id... trackIds) {
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
}
@Override
public void onConfirmDeleteDone(long[] trackIds) {
public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false;
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) {
if (trackId == recordingTrackId) {
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) {
stopRecording = true;
break;
}
@@ -75,7 +79,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
startActivityForResult(intent, DELETE_REQUEST_CODE);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks;
import android.os.Bundle;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -12,7 +11,7 @@ public class HelpActivity extends AbstractActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content));
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
}
@Override
@@ -48,32 +48,33 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private List<Long> markerIds;
private List<Waypoint.Id> waypointIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId == -1L) {
Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if (waypointId == null) {
Log.d(TAG, "invalid marker id");
finish();
return;
}
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
markerIds = new ArrayList<>();
waypointIds = new ArrayList<>();
int markerIndex = -1;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) {
//TODO Load only waypointIds, not the whole waypoint
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
waypointIds.add(currentMarker.getId());
if (waypointId.equals(currentMarker.getId())) {
markerIndex = waypointIds.size() - 1;
}
cursor.moveToNext();
@@ -126,7 +127,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override
@NonNull
public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position));
return MarkerDetailFragment.newInstance(waypointIds.get(position));
}
@Nullable
@@ -137,7 +138,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override
public int getCount() {
return markerIds.size();
return waypointIds.size();
}
}
}
@@ -42,6 +42,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -64,7 +65,7 @@ public class MarkerEditActivity extends AbstractActivity {
private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId;
private Track.Id trackId;
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private Waypoint waypoint;
@@ -85,8 +86,8 @@ public class MarkerEditActivity extends AbstractActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
@@ -111,7 +112,7 @@ public class MarkerEditActivity extends AbstractActivity {
Button cancel = findViewById(R.id.marker_edit_cancel);
cancel.setOnClickListener(v -> finish());
final boolean isNewMarker = markerId == -1L;
final boolean isNewMarker = waypointId == null;
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
Button done = findViewById(R.id.marker_edit_done);
@@ -126,7 +127,7 @@ public class MarkerEditActivity extends AbstractActivity {
});
if (isNewMarker) {
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
int nextWaypointNumber = trackId == null ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
}
@@ -135,7 +136,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointMarkerType.setText("");
waypointDescription.setText("");
} else {
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
waypoint = new ContentProviderUtils(this).getWaypoint(waypointId);
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
finish();
@@ -151,7 +152,9 @@ public class MarkerEditActivity extends AbstractActivity {
if (savedInstanceState != null) {
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
if (waypoint != null) {
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
}
}
if (photoUri != null) {
setWaypointImageView(photoUri);
@@ -258,8 +261,8 @@ public class MarkerEditActivity extends AbstractActivity {
/**
* Returns the trackId. If trackId == -1 then get it from waypoint object.
*/
private long getTrackId() {
return trackId == -1 ? waypoint.getTrackId() : trackId;
private Track.Id getTrackId() {
return trackId == null ? waypoint.getTrackId() : trackId;
}
/**
@@ -32,10 +32,10 @@ import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
@@ -57,16 +57,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private static final String TAG = MarkerListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@@ -117,19 +112,19 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> {
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Waypoint.Id(id));
startActivity(intent);
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@@ -163,14 +158,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
null, null, null);
}
return ContentProviderUtils.getWaypointsLoader(MarkerListActivity.this, track != null ? track.getId() : null);
}
@Override
@@ -223,7 +211,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
insertMarkerMenuItem.setVisible(track != null && track.getId() == recordingTrackId && !recordingTrackPaused);
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused);
return super.onPrepareOptionsMenu(menu);
}
@@ -241,30 +229,35 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param markerIds the marker ids
* @param itemId the menu item id
* @param longWaypointIds the marker ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long[] markerIds) {
private boolean handleContextItem(int itemId, long... longWaypointIds) {
Waypoint.Id[] waypointIds = new Waypoint.Id[longWaypointIds.length];
for (int i = 0; i < longWaypointIds.length; i++) {
waypointIds[i] = new Waypoint.Id(longWaypointIds[i]);
}
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
if (markerIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(markerIds[0]));
if (waypointIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(waypointIds[0]));
}
return true;
case R.id.list_context_menu_edit:
if (markerIds.length == 1) {
if (waypointIds.length == 1) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointIds[0]);
startActivity(intent);
}
return true;
case R.id.list_context_menu_delete:
if (markerIds.length > 1 && markerIds.length == listView.getCount()) {
markerIds = new long[]{-1L};
if (waypointIds.length > 1 && waypointIds.length == listView.getCount()) {
waypointIds = null;
}
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointIds);
return true;
case R.id.list_context_menu_select_all:
for (int i = 0; i < listView.getCount(); i++) {
@@ -97,7 +97,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
@@ -165,7 +165,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
searchEngine = new SearchEngine(contentProviderUtils);
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
@@ -203,7 +203,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
listView.setEmptyView(findViewById(R.id.search_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> {
Map<String, Object> item = arrayAdapter.getItem(position);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
if (markerId != null) {
@@ -270,22 +270,22 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
return false;
}
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Waypoint.Id waypointId = (Waypoint.Id) item.get(MARKER_ID_FIELD);
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
return true;
case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
intent = IntentUtils.newShareFileIntent(this, trackId);
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.list_context_menu_edit:
if (markerId != null) {
if (waypointId != null) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
} else {
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
@@ -296,10 +296,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
finish();
return true;
case R.id.list_context_menu_delete:
if (markerId != null) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), new long[]{markerId});
if (waypointId != null) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointId);
} else {
deleteTracks(new long[]{trackId});
deleteTracks(trackId);
}
return true;
default:
@@ -331,7 +331,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
* @param textQuery the query
*/
private void doSearch(String textQuery) {
SearchQuery query = new SearchQuery(textQuery, null, -1L, System.currentTimeMillis());
SearchQuery query = new SearchQuery(textQuery, null, null, System.currentTimeMillis());
SortedSet<ScoredResult> scoredResults = searchEngine.search(query);
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
@@ -372,8 +372,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable.
String trackName = null;
long trackId = waypoint.getTrackId();
if (trackId != -1L) {
Track.Id trackId = waypoint.getTrackId();
if (!trackId.isValid()) {
Track track = contentProviderUtils.getTrack(trackId);
if (track != null) {
trackName = track.getName();
@@ -8,6 +8,9 @@ import android.util.Pair;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -26,7 +29,7 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long[] trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
Set<Track.Id> trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
showTrackTrackfileFormat(this, trackFileFormat, trackIds);
@@ -39,14 +42,14 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
* @param context the context
* @param trackIds the track ids
*/
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, long[] trackIds) {
if (trackIds.length == 0) {
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, Set<Track.Id> trackIds) {
if (trackIds.size() == 0) {
return;
}
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds.toArray(new Track.Id[0])[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
@@ -32,6 +32,11 @@ public class Startup extends Application {
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
}
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
if (PreferencesUtils.isRecording(this)) {
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
//In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) {
@@ -16,15 +16,24 @@
package de.dennisguse.opentracks;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -46,6 +55,7 @@ public class TrackController {
private final ImageButton recordImageButton;
private final ImageButton stopImageButton;
private final boolean alwaysShow;
private ButtonDelay buttonDelay;
private boolean isRecording;
private boolean isPaused;
@@ -65,6 +75,7 @@ public class TrackController {
}
};
@SuppressLint("ClickableViewAccessibility")
TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
this.activity = activity;
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
@@ -75,10 +86,91 @@ public class TrackController {
totalTimeTextView = activity.findViewById(R.id.track_controller_total_time);
recordImageButton = activity.findViewById(R.id.track_controller_record);
recordImageButton.setOnClickListener(recordListener);
recordImageButton.setOnTouchListener((view, motionEvent) -> onRecordTouch(activity, recordListener, motionEvent));
stopImageButton = activity.findViewById(R.id.track_controller_stop);
stopImageButton.setOnClickListener(stopListener);
stopImageButton.setOnTouchListener((view, motionEvent) -> onStopTouch(activity, stopListener, motionEvent));
}
private boolean onRecordTouch(final Activity activity, final OnClickListener recordListener, final MotionEvent motionEvent) {
if (isRecording && !isPaused) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
buttonDelay = new ButtonDelay(activity, recordImageButton, R.drawable.ic_button_pause_anim, R.string.hold_to_pause, recordListener);
new Thread(buttonDelay).start();
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP ) {
recordImageButton.setImageResource(R.drawable.ic_button_pause);
if (buttonDelay != null) {
buttonDelay.canceled = true;
}
return true;
}
} else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
recordListener.onClick(null);
return true;
}
return false;
}
private boolean onStopTouch(final Activity activity, final OnClickListener stopListener, final MotionEvent motionEvent) {
if (isRecording) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
buttonDelay = new ButtonDelay(activity, stopImageButton, R.drawable.ic_button_stop_anim, R.string.hold_to_stop, stopListener);
new Thread(buttonDelay).start();
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP ) {
stopImageButton.setImageResource(R.drawable.ic_button_stop);
if (buttonDelay != null) {
buttonDelay.canceled = true;
}
return true;
}
}
return false;
}
private static class ButtonDelay implements Runnable {
private boolean canceled = false;
private final ImageButton imageButton;
private final Activity activity;
private final OnClickListener clickListener;
private final int delayMillis;
private final Drawable drawable;
private final int delayMessageId;
private ButtonDelay(final Activity activity, final ImageButton imageButton, final int animDrawableId, final int delayMessageId, final OnClickListener clickListener) {
this.activity = activity;
this.clickListener = clickListener;
this.delayMillis = activity.getResources().getInteger(R.integer.buttonDelayMillis);
this.imageButton = imageButton;
this.drawable = ContextCompat.getDrawable(activity, animDrawableId);
this.delayMessageId = delayMessageId;
}
@Override
public void run() {
activity.runOnUiThread(()->{
imageButton.setImageDrawable(drawable);
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
ActivityUtils.vibrate(activity, 150);
ActivityUtils.toast(activity, delayMessageId, Toast.LENGTH_SHORT, Gravity.TOP);
});
try {
Thread.sleep(delayMillis);
} catch (InterruptedException ignored) {
}
if (!canceled) {
activity.runOnUiThread(()-> {
clickListener.onClick(null);
ActivityUtils.vibrate(activity, 1000);
});
}
}
}
public void update(boolean recording, boolean paused) {
@@ -95,10 +187,10 @@ public class TrackController {
return;
}
recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.button_pause : R.drawable.button_record);
recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record));
stopImageButton.setImageResource(isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled);
stopImageButton.setImageResource(isRecording ? R.drawable.ic_button_stop : R.drawable.ic_button_stop_disabled);
stopImageButton.setEnabled(isRecording);
statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
@@ -21,6 +21,9 @@ import android.os.Bundle;
import android.os.PowerManager;
import android.view.View;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.SystemUtils;
@@ -33,7 +36,7 @@ public class TrackDeleteActivity extends AbstractActivity {
public static final String EXTRA_TRACK_IDS = "track_ids";
private long[] trackIds;
private List<Track.Id> trackIds;
private Thread deleteThread;
@@ -44,13 +47,13 @@ public class TrackDeleteActivity extends AbstractActivity {
setResult(RESULT_CANCELED);
Intent intent = getIntent();
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
deleteThread = new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
for (long id : trackIds) {
for (Track.Id id : trackIds) {
if (Thread.interrupted()) {
break;
}
@@ -64,9 +64,9 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId == -1L) {
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (!trackId.isValid()) {
Log.e(TAG, "invalid trackId");
finish();
return;
@@ -24,6 +24,7 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
@@ -42,24 +43,23 @@ import androidx.core.app.ActivityCompat;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.ServiceUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/**
* An activity displaying a list of tracks.
@@ -70,10 +70,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
// The following are set in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
@@ -81,12 +77,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private TrackController trackController;
private ListView listView;
private ResourceCursorAdapter resourceCursorAdapter;
private GpsStatusValue gpsStatusValue;
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null,
null, TrackUtils.TRACK_SORT_ORDER);
return ContentProviderUtils.getTracksCursorLoader(TrackListActivity.this);
}
@Override
@@ -102,7 +98,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Preferences
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId = null;
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -123,8 +119,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
@@ -162,28 +157,37 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
};
private boolean startGps = false; // true to start gps
// Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
// After binding changes (e.g., becomes available), update the total time in trackController.
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
if (!startGps) {
return;
}
// After binding changes (e.g., becomes available), update the total time in trackController.
runOnUiThread(() -> trackController.update(isRecording, recordingTrackPaused));
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) {
Log.d(TAG, "service not available to start gps or a new recording");
gpsStatusValue = GpsStatusValue.GPS_NONE;
return;
}
if (startGps) {
service.startGps();
startGps = false;
// Get GPS status and listen GPS status changes.
gpsStatusValue = service.getGpsStatus();
updateGpsMenuItem(true, isRecording);
service.addListener(newStatus -> {
gpsStatusValue = newStatus;
updateGpsMenuItem(true, isRecording);
});
if (isGpsStarted()) {
return;
}
service.startGps();
gpsStatusValue = GpsStatusValue.GPS_ENABLED;
updateGpsMenuItem(true, isRecording);
}
};
@@ -212,6 +216,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gpsStatusValue = GpsStatusValue.GPS_NONE;
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this);
@@ -232,14 +238,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
listView.setOnItemClickListener((parent, view, position, trackId) -> {
Intent newIntent;
if (trackId == recordingTrackId) {
if (trackId == recordingTrackId.getId()) {
// Is recording -> open record activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
} else {
// Not recording -> open detail activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
}
startActivity(newIntent);
});
@@ -256,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
long trackId = cursor.getLong(idIndex);
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
boolean isRecording = trackId == recordingTrackId;
String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon);
@@ -346,7 +352,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this);
boolean isGpsStarted = isGpsStarted();
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
updateMenuItems(isGpsStarted, isRecording);
@@ -366,10 +372,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
} else {
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
// Invoke trackRecordingService
if (startGps) {
if (!isGpsStarted()) {
trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run();
} else {
@@ -425,6 +429,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Do nothing
}
private boolean isGpsStarted() {
return gpsStatusValue != GpsStatusValue.GPS_NONE && gpsStatusValue != GpsStatusValue.GPS_DISABLED;
}
private void requestGPSPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE);
@@ -432,17 +440,30 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
}
/**
* Updates the menu items.
* Updates the menu items with not fixed icon for gps option.
*
* @param isGpsStarted true if gps is started
* @param isRecording true if recording
*/
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
updateGpsMenuItem(isGpsStarted, isRecording);
}
/**
* Updates the menu items with the icon specified.
*
* @param isGpsStarted true if gps is started
* @param isRecording true if recording
*/
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
if (startGpsMenuItem != null) {
startGpsMenuItem.setVisible(!isRecording);
if (!isRecording) {
startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp);
startGpsMenuItem.setIcon(isGpsStarted ? gpsStatusValue.icon : R.drawable.ic_gps_off_24dp);
if (startGpsMenuItem.getIcon() instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) startGpsMenuItem.getIcon()).start();
}
}
}
}
@@ -450,15 +471,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param trackIds the track ids
* @param itemId the menu item id
* @param longTrackIds the track ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long[] trackIds) {
private boolean handleContextItem(int itemId, long... longTrackIds) {
Track.Id[] trackIds = new Track.Id[longTrackIds.length];
for (int i = 0; i < longTrackIds.length; i++) {
trackIds[i] = new Track.Id(longTrackIds[i]);
}
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, trackIds, false);
IntentDashboardUtils.startDashboard(this, false, trackIds);
return true;
case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, trackIds);
@@ -66,7 +66,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
private ViewPager pager;
// From intent.
private long trackId;
private Track.Id trackId;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -133,7 +133,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
// Update UI
this.invalidateOptionsMenu();
if (trackId != -1L) {
if (trackId.isValid()) {
trackDataHub.loadTrack(trackId);
}
}
@@ -182,12 +182,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
Intent intent;
switch (item.getItemId()) {
case R.id.track_detail_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
intent = IntentUtils.newShareFileIntent(this, trackId);
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, false);
IntentDashboardUtils.startDashboard(this, false, trackId);
return true;
case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
@@ -200,7 +200,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
startActivity(intent);
return true;
case R.id.track_detail_delete:
deleteTracks(new long[]{trackId});
deleteTracks(trackId);
return true;
case R.id.track_detail_resume_track:
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
@@ -238,12 +238,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
}
private void handleIntent(Intent intent) {
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId != -1L) {
Waypoint.Id waypointId = intent.getParcelableExtra(EXTRA_MARKER_ID);
if (waypointId != null) {
// Use the trackId from the marker
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
if (waypoint == null) {
finish();
return;
@@ -251,7 +251,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
trackId = waypoint.getTrackId();
}
if (trackId == -1L) {
if (trackId == null) {
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
finish();
}
@@ -60,7 +60,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private TrackController trackController;
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
private long trackId;
private Track.Id trackId;
// Preferences
private boolean recordingTrackPaused;
@@ -78,7 +78,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
if (!service.isRecording()) {
// Starts or resumes a track.
if (trackId == -1L) {
if (trackId == null) {
// trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack();
} else {
@@ -161,13 +161,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
trackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
trackId = null;
if (savedInstanceState != null) {
//Activity was recreated.
trackId = savedInstanceState.getLong(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
trackId = savedInstanceState.getParcelable(EXTRA_TRACK_ID);
} else {
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT && contentProviderUtils.getTrack(trackId) == null) {
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();
}
}
@@ -247,7 +248,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// Update UI
this.invalidateOptionsMenu();
if (trackId != -1L) {
if (trackId != null) {
trackDataHub.loadTrack(trackId);
trackController.onResume(true, recordingTrackPaused);
}
@@ -265,7 +266,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
outState.putLong(EXTRA_TRACK_ID, trackId);
outState.putParcelable(EXTRA_TRACK_ID, trackId);
}
@Override
@@ -314,7 +315,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
startActivity(intent);
return true;
case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, true);
IntentDashboardUtils.startDashboard(this, true, trackId);
return true;
case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
@@ -43,7 +43,7 @@ public class ChartPoint {
elevation *= UnitConversions.M_TO_FT;
}
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MPS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
@@ -185,7 +185,7 @@ public class DescriptionGenerator {
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH;
double speedInKmHr = speed * UnitConversions.MPS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak);
@@ -123,8 +123,8 @@ public class SearchEngine {
}
// Finally, use arbitrary ordering, by ID.
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
long id1 = r1.track != null ? r1.track.getId().getId() : r1.waypoint.getId().getId();
long id2 = r2.track != null ? r2.track.getId().getId() : r2.waypoint.getId().getId();
long idDiff = id2 - id1;
return Long.signum(idDiff);
};
@@ -290,7 +290,7 @@ public class SearchEngine {
score *= getTimeBoost(query, location.getTime());
// Score waypoints in the currently-selected track higher (searching inside the current track).
if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) {
if (waypoint.getTrackId() != null && waypoint.getTrackId().equals(query.currentTrackId)) {
score *= CURRENT_TRACK_WAYPOINT_PROMOTION;
}
@@ -382,10 +382,10 @@ public class SearchEngine {
public static class SearchQuery {
final String textQuery;
final Location currentLocation;
final long currentTrackId;
final Track.Id currentTrackId;
final long currentTimestamp;
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) {
public SearchQuery(String textQuery, Location currentLocation, Track.Id currentTrackId, long currentTimestamp) {
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
this.currentLocation = currentLocation;
this.currentTrackId = currentTrackId;
@@ -417,8 +417,8 @@ public class SearchEngine {
@Override
public String toString() {
return "ScoredResult ["
+ (track != null ? ("trackId=" + track.getId() + ", ") : "")
+ (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "")
+ (track != null ? ("trackId=" + track.getId().getId() + ", ") : "")
+ (waypoint != null ? ("wptId=" + waypoint.getId().getId() + ", ") : "")
+ "score=" + score + "]";
}
}
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
@@ -78,8 +79,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private Handler handler;
// Preference values
private long selectedTrackId;
private long recordingTrackId;
private Track.Id selectedTrackId;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
// Track points sampling state
@@ -174,10 +175,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
handler = null;
}
public void loadTrack(final long trackId) {
public void loadTrack(final @NonNull Track.Id trackId) {
runInHandlerThread(() -> {
if (trackId == selectedTrackId) {
Log.i(TAG, "Not reloading track " + trackId);
if (trackId.equals(selectedTrackId)) {
Log.i(TAG, "Not reloading track " + trackId.getId());
return;
}
selectedTrackId = trackId;
@@ -212,14 +213,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
* Returns true if the selected track is recording.
*/
public boolean isSelectedTrackRecording() {
return selectedTrackId == recordingTrackId && PreferencesUtils.isRecording(recordingTrackId);
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && PreferencesUtils.isRecording(recordingTrackId);
}
/**
* Returns true if the selected track is paused.
*/
public boolean isSelectedTrackPaused() {
return selectedTrackId == recordingTrackId && recordingTrackPaused;
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused;
}
@Override
@@ -311,7 +312,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
trackDataListener.clearWaypoints();
}
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, null, MAX_DISPLAYED_WAYPOINTS)) {
if (cursor != null && cursor.moveToFirst()) {
do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
@@ -356,6 +357,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
if (selectedTrackId == null) {
Log.w(TAG, "This should not happen, but it does"); //TODO
return;
}
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
@@ -16,9 +16,17 @@
package de.dennisguse.opentracks.content.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import java.util.UUID;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* A track.
@@ -28,7 +36,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
*/
public class Track {
private long id = -1L;
private Track.Id id;
private UUID uuid = UUID.randomUUID();
private String name = "";
@@ -42,11 +50,15 @@ public class Track {
public Track() {
}
public long getId() {
/**
* May be null if the track was not loaded from the database.
*/
public @Nullable
Id getId() {
return id;
}
public void setId(long id) {
public void setId(Id id) {
this.id = id;
}
@@ -97,4 +109,66 @@ public class Track {
public void setTrackStatistics(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
}
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Track.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<Track.Id>() {
public Track.Id createFromParcel(Parcel in) {
return new Track.Id(in.readLong());
}
public Track.Id[] newArray(int size) {
return new Track.Id[size];
}
};
}
}
@@ -33,7 +33,7 @@ public interface TrackPointsColumns extends BaseColumns {
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
String DEFAULT_SORT_ORDER = "_id";
String DEFAULT_SORT_ORDER = _ID;
// Columns
String TRACKID = "trackid";
@@ -32,7 +32,7 @@ public interface TracksColumns extends BaseColumns {
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
String DEFAULT_SORT_ORDER = "_id";
String DEFAULT_SORT_ORDER = _ID;
// Columns
String UUID = "uuid";
@@ -18,10 +18,17 @@ package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Objects;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* A waypoint.
*
@@ -30,12 +37,12 @@ import androidx.annotation.VisibleForTesting;
*/
public final class Waypoint {
private long id = -1L;
private Id id;
private String name = "";
private String description = "";
private String category = "";
private String icon = "";
private long trackId = -1L;
private Track.Id trackId;
private double length = 0.0;
private long duration = 0;
private Location location;
@@ -51,7 +58,7 @@ public final class Waypoint {
this.location = location;
}
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, @NonNull Location location, String photoUrl) {
public Waypoint(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull Location location, String photoUrl) {
this.name = name;
this.description = description;
this.category = category;
@@ -63,11 +70,15 @@ public final class Waypoint {
this.photoUrl = photoUrl;
}
public long getId() {
/**
* May be null if the waypoint was not loaded from the database.
*/
public @Nullable
Id getId() {
return id;
}
public void setId(long id) {
public void setId(Id id) {
this.id = id;
}
@@ -103,11 +114,11 @@ public final class Waypoint {
this.icon = icon;
}
public long getTrackId() {
public Track.Id getTrackId() {
return trackId;
}
public void setTrackId(long trackId) {
public void setTrackId(Track.Id trackId) {
this.trackId = trackId;
}
@@ -147,4 +158,66 @@ public final class Waypoint {
public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl);
}
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Waypoint.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<Id>() {
public Id createFromParcel(Parcel in) {
return new Id(in.readLong());
}
public Id[] newArray(int size) {
return new Id[size];
}
};
}
}
@@ -33,7 +33,7 @@ public interface WaypointsColumns extends BaseColumns {
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
String DEFAULT_SORT_ORDER = "_id";
String DEFAULT_SORT_ORDER = _ID;
// Columns
String NAME = "name"; // waypoint name
@@ -23,13 +23,19 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.android.ContentResolverWrapper;
@@ -42,6 +48,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.TrackUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
/**
@@ -56,7 +63,8 @@ public class ContentProviderUtils {
private static final int MAX_LATITUDE = 90000000;
// The authority (the first part of the URI) for the app's content provider.
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
@VisibleForTesting
public static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
// The base URI for the app's content provider.
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
@@ -86,8 +94,8 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
public void clearTrack(Context context, long trackId) {
deleteTrackPointsAndWaypoints(context, trackId);
public void clearTrack(Track.Id trackId) {
deleteTrackPointsAndWaypoints(trackId);
Track track = new Track();
track.setId(trackId);
updateTrack(track);
@@ -118,7 +126,7 @@ public class ContentProviderUtils {
Track track = new Track();
TrackStatistics trackStatistics = track.getTrackStatistics();
if (!cursor.isNull(idIndex)) {
track.setId(cursor.getLong(idIndex));
track.setId(new Track.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(uuidIndex)) {
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
@@ -181,14 +189,17 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
public void deleteTrack(Context context, long trackId) {
deleteTrackPointsAndWaypoints(context, trackId);
public void deleteTrack(Context context, Track.Id trackId) {
if (trackId == null) {
return;
}
deleteTrackPointsAndWaypoints(trackId);
// Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
// Delete track last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId)});
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
}
/**
@@ -196,21 +207,15 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
private void deleteTrackPointsAndWaypoints(Context context, long trackId) {
String where = TrackPointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, where, selectionArgs);
private void deleteTrackPointsAndWaypoints(@NonNull Track.Id trackId) {
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)});
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, TrackPointsColumns.TRACKID + "=?", selectionArgs);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", selectionArgs);
}
/**
* Gets all the tracks.
*
* @return the tracks do not have any trackPoints attached.
*/
@VisibleForTesting
public List<Track> getAllTracks() {
public List<Track> getTracks() {
ArrayList<Track> tracks = new ArrayList<>();
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
@@ -223,6 +228,17 @@ public class ContentProviderUtils {
return tracks;
}
public static Loader<Cursor> getTracksCursorLoader(Context context) {
String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
return new CursorLoader(context, TracksColumns.CONTENT_URI, PROJECTION, null, null, TrackUtils.TRACK_SORT_ORDER);
}
@Deprecated
//TODO Do not use; rather get the proper TrackId (e.g., resume a track might give you some weird effects).
@VisibleForTesting
public Track getLastTrack() {
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
// Using the same order as shown in the track list
@@ -235,13 +251,25 @@ public class ContentProviderUtils {
/**
* @param trackId the track id.
* @return the track doesn't have any trackPoints attached
*/
public Track getTrack(long trackId) {
if (trackId < 0) {
public Track getTrack(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
return null;
}
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId)}, TracksColumns._ID)) {
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
}
return null;
}
/**
* @param trackUUID the track uuid.
*/
public Track getTrack(@NonNull UUID trackUUID) {
String trackUUIDsearch = UUIDUtils.toHex(trackUUID);
try (Cursor cursor = getTrackCursor("hex(" + TracksColumns.UUID + ")=?", new String[]{trackUUIDsearch}, null)) {
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
@@ -279,7 +307,7 @@ public class ContentProviderUtils {
* @param track the track
*/
public void updateTrack(Track track) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())});
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().getId())});
}
private ContentValues createContentValues(Track track) {
@@ -287,8 +315,8 @@ public class ContentProviderUtils {
TrackStatistics trackStatistics = track.getTrackStatistics();
// Value < 0 indicates no id is available
if (track.getId() >= 0) {
values.put(TracksColumns._ID, track.getId());
if (track.getId() != null && track.getId().isValid()) {
values.put(TracksColumns._ID, track.getId().getId());
}
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
values.put(TracksColumns.NAME, track.getName());
@@ -354,7 +382,7 @@ public class ContentProviderUtils {
Waypoint waypoint = new Waypoint(location);
if (!cursor.isNull(idIndex)) {
waypoint.setId(cursor.getLong(idIndex));
waypoint.setId(new Waypoint.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(nameIndex)) {
waypoint.setName(cursor.getString(nameIndex));
@@ -369,7 +397,7 @@ public class ContentProviderUtils {
waypoint.setIcon(cursor.getString(iconIndex));
}
if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex));
waypoint.setTrackId(new Track.Id(cursor.getLong(trackIdIndex)));
}
if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex));
@@ -384,10 +412,10 @@ public class ContentProviderUtils {
return waypoint;
}
public void deleteWaypoint(Context context, long waypointId) {
public void deleteWaypoint(Context context, Waypoint.Id waypointId) {
final Waypoint waypoint = getWaypoint(waypointId);
deleteWaypointPhoto(context, waypoint);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId.getId())});
}
/**
@@ -396,13 +424,13 @@ public class ContentProviderUtils {
* @param trackId the track id
* @return -1 if not able to get the next waypoint number.
*/
public int getNextWaypointNumber(long trackId) {
if (trackId < 0) {
public int getNextWaypointNumber(Track.Id trackId) {
if (!trackId.isValid()) {
return -1;
}
String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
if (cursor != null) {
return cursor.getCount();
@@ -411,12 +439,12 @@ public class ContentProviderUtils {
return -1;
}
public Waypoint getWaypoint(long waypointId) {
if (waypointId < 0) {
public Waypoint getWaypoint(Waypoint.Id waypointId) {
if (!waypointId.isValid()) {
return null;
}
try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?",
new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) {
new String[]{Long.toString(waypointId.getId())}, WaypointsColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor);
}
@@ -442,30 +470,30 @@ public class ContentProviderUtils {
* The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param minWaypointId the minimum waypoint id. -1L to ignore
* @param minWaypointId the minimum waypoint id. null to ignore
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
*/
public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) {
if (trackId < 0) {
public Cursor getWaypointCursor(Track.Id trackId, @Nullable Waypoint.Id minWaypointId, int maxWaypoints) {
if (!trackId.isValid()) {
return null;
}
String selection;
String[] selectionArgs;
if (minWaypointId >= 0) {
if (minWaypointId != null && minWaypointId.isValid()) {
selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)};
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minWaypointId.getId())};
} else {
selection = WaypointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)};
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints);
}
@VisibleForTesting
public List<Waypoint> getWaypoints(long trackId) {
public List<Waypoint> getWaypoints(Track.Id trackId) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
try (Cursor cursor = getWaypointCursor(trackId, -1L, -1)) {
try (Cursor cursor = getWaypointCursor(trackId, null, -1)) {
if (cursor.moveToFirst()) {
do {
waypoints.add(createWaypoint(cursor));
@@ -475,19 +503,34 @@ public class ContentProviderUtils {
return waypoints;
}
public static Loader<Cursor> getWaypointsLoader(Context context, @Nullable Track.Id trackId) {
final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
if (trackId != null) {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(trackId.getId())}, null);
} else {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION, null, null, null);
}
}
/**
* Gets the number of waypoints for a track.
*
* @param trackId the track id
*/
public int getWaypointCount(long trackId) {
if (trackId < 0) {
public int getWaypointCount(Track.Id trackId) {
if (!trackId.isValid()) {
return 0;
}
String[] projection = new String[]{"count(*) AS count"};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) {
if (cursor == null) {
return 0;
@@ -503,8 +546,8 @@ public class ContentProviderUtils {
* @param waypoint the waypoint
* @return the content provider URI of the inserted waypoint.
*/
public Uri insertWaypoint(Waypoint waypoint) {
waypoint.setId(-1L);
public Uri insertWaypoint(@NonNull Waypoint waypoint) {
waypoint.setId(null);
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
}
@@ -539,22 +582,23 @@ public class ContentProviderUtils {
if (!updatedWaypoint.hasPhoto()) {
deleteWaypointPhoto(context, savedWaypoint);
}
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())});
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId().getId())});
return rows == 1;
}
ContentValues createContentValues(Waypoint waypoint) {
ContentValues createContentValues(@NonNull Waypoint waypoint) {
ContentValues values = new ContentValues();
// Value < 0 indicates no id is available
if (waypoint.getId() >= 0) {
values.put(WaypointsColumns._ID, waypoint.getId());
if (waypoint.getId() != null && waypoint.getId().isValid()) {
values.put(WaypointsColumns._ID, waypoint.getId().getId());
}
values.put(WaypointsColumns.NAME, waypoint.getName());
values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription());
values.put(WaypointsColumns.CATEGORY, waypoint.getCategory());
values.put(WaypointsColumns.ICON, waypoint.getIcon());
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId());
if (waypoint.getTrackId() != null) {
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId().getId());
}
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
@@ -647,7 +691,7 @@ public class ContentProviderUtils {
* @return the number of trackPoints inserted
*/
//TODO Only used for testing and file import; might be better to replace it.
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, long trackId) {
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.length];
for (int i = 0; i < values.length; i++) {
values[i] = createContentValues(trackPoints[i], trackId);
@@ -657,23 +701,23 @@ public class ContentProviderUtils {
/**
* Gets the first location id for a track.
* Returns -1L if it doesn't exist.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
@Deprecated
public long getFirstTrackPointId(long trackId) {
if (trackId < 0) {
return -1L;
public Track.Id getFirstTrackPointId(Track.Id trackId) {
if (!trackId.isValid()) {
return null;
}
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
return new Track.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return -1L;
return null;
}
/**
@@ -683,12 +727,13 @@ public class ContentProviderUtils {
* @param trackId the track id
*/
@Deprecated
public long getLastTrackPointId(long trackId) {
if (trackId < 0) {
public long getLastTrackPointId(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
Log.w(TAG, "Fix callers who do this.");
return -1L;
}
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -704,12 +749,12 @@ public class ContentProviderUtils {
* @param location the location
* @return trackPoint id if the location is in the track. -1L otherwise.
*/
public long getTrackPointId(long trackId, Location location) {
if (trackId < 0) {
public long getTrackPointId(Track.Id trackId, Location location) {
if (!trackId.isValid()) {
return -1L;
}
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId), Long.toString(location.getTime())};
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -735,8 +780,8 @@ public class ContentProviderUtils {
* @param maxLocations maximum number of locations to return. -1 for no limit
* @param descending true to sort the result in descending order (latest location first)
*/
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (trackId < 0) {
public Cursor getTrackPointCursor(Track.Id trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (!trackId.isValid()) {
return null;
}
@@ -745,10 +790,10 @@ public class ContentProviderUtils {
if (startTrackPointId >= 0) {
String comparison = descending ? "<=" : ">=";
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)};
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)};
} else {
selection = TrackPointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)};
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
String sortOrder = TrackPointsColumns._ID;
@@ -768,12 +813,12 @@ public class ContentProviderUtils {
* @param trackId the track id
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(long trackId) {
if (trackId < 0) {
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
if (!trackId.isValid()) {
return null;
}
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
}
@@ -784,7 +829,7 @@ public class ContentProviderUtils {
* @param trackId the track id
* @return the content provider URI of the inserted trackPoint
*/
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
public Uri insertTrackPoint(TrackPoint trackPoint, Track.Id trackId) {
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
}
@@ -794,9 +839,9 @@ public class ContentProviderUtils {
* @param trackPoint the trackPoint
* @param trackId the track id
*/
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId);
values.put(TrackPointsColumns.TRACKID, trackId.getId());
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
@@ -837,7 +882,7 @@ public class ContentProviderUtils {
* @param startTrackPointId the starting trackPoint id. -1L to ignore
* @param descending true to sort the result in descending order (latest location first)
*/
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) {
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final long startTrackPointId, final boolean descending) {
return new TrackPointIterator(this, trackId, startTrackPointId, descending);
}
@@ -863,7 +908,7 @@ public class ContentProviderUtils {
}
@VisibleForTesting
public List<TrackPoint> getTrackPoints(long trackId) {
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
List<TrackPoint> trackPoints = null;
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
@@ -894,13 +939,23 @@ public class ContentProviderUtils {
this.defaultCursorBatchSize = defaultCursorBatchSize;
}
public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
ids[i] = trackIds[i].getId();
}
return formatIdListForUri(ids);
}
/**
* Formats an array of IDs as comma separated string value
*
* @param ids array with IDs
* @return comma separated list of ids
*/
public static String formatIdListForUri(long[] ids) {
private static String formatIdListForUri(long[] ids) {
StringBuilder idsPathSegment = new StringBuilder();
for (long id : ids) {
if (idsPathSegment.length() > 0) {
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
*
* @author Leif Hendrik Wilden
*/
public abstract class CustomContentProvider extends ContentProvider {
public class CustomContentProvider extends ContentProvider {
private static final String TAG = CustomContentProvider.class.getSimpleName();
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.UUID;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
@@ -20,13 +21,14 @@ import de.dennisguse.opentracks.util.UUIDUtils;
* Database helper for creating and upgrading the database.
*/
@VisibleForTesting
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 26;
private static final String DATABASE_NAME = "database.db";
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
public CustomSQLiteOpenHelper(Context context) {
this(context, DATABASE_NAME);
@@ -161,7 +163,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
if (cursor.moveToFirst()) {
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
do {
long trackId = cursor.getLong(trackIdIndex);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
ContentValues cv = new ContentValues();
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)});
@@ -17,8 +17,11 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Track;
@@ -29,9 +32,9 @@ import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
/**
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
* The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}.
*
* <p>
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
*
* <p>
* Explanation:
* Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}.
* Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps.
@@ -70,14 +73,20 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
}
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.length == 0) {
public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
Set<Track.Id> trackIds = new HashSet<>(1);
trackIds.add(trackId);
return createURI(trackIds, trackName, trackFileFormat);
}
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.size() == 0) {
throw new UnsupportedOperationException();
}
StringBuilder trackIdBuilder = new StringBuilder();
for (long trackId : trackIds) {
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER);
for (Track.Id trackId : trackIds) {
trackIdBuilder.append(trackId.getId()).append(TRACKID_DELIMITER);
}
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
@@ -89,18 +98,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
return new Pair<>(uri, mime);
}
static long[] parseURI(Uri uri) {
static Set<Track.Id> parseURI(Uri uri) {
List<String> uriPaths = uri.getPathSegments();
if (uriPaths == null || uriPaths.size() < 3) {
Log.d(TAG, "URI does not contain any trackIds.");
return new long[]{};
return new HashSet<>();
}
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
long[] trackIds = new long[uriTrackIds.length];
for (int i = 0; i < trackIds.length; i++) {
trackIds[i] = Long.parseLong(uriTrackIds[i]);
Set<Track.Id> trackIds = new HashSet<>();
for (String uriTrackId : uriTrackIds) {
trackIds.add(new Track.Id(Long.parseLong(uriTrackId)));
}
return trackIds;
}
@@ -202,17 +211,17 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
long[] trackIds = parseURI(uri);
final Track[] tracks = new Track[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
Set<Track.Id> trackIds = parseURI(uri);
final ArrayList<Track> tracks = new ArrayList<>();
for (Track.Id trackId : trackIds) {
tracks.add(contentProviderUtils.getTrack(trackId));
}
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks);
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext());
PipeDataWriter<String> pipeDataWriter = (PipeDataWriter<String>) (output, uri1, mimeType, opts, args) -> {
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
trackExporter.writeTrack(getContext(), fileOutputStream);
trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
} catch (IOException e) {
Log.w(TAG, "there occurred an error while sharing a file: " + e);
}
@@ -6,6 +6,7 @@ import android.util.Log;
import java.util.Iterator;
import java.util.NoSuchElementException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
@@ -16,14 +17,14 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils;
private final long trackId;
private final Track.Id trackId;
private final boolean descending;
private final CachedTrackPointsIndexes indexes;
private long lastTrackPointId = -1L;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending) {
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, long startTrackPointId, boolean descending) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
this.descending = descending;
@@ -25,6 +25,7 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.DialogUtils;
/**
@@ -44,9 +45,9 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
*
* @param trackIds list of track ids to delete.
*/
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
public static void showDialog(FragmentManager fragmentManager, Track.Id... trackIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
bundle.putParcelableArray(KEY_TRACK_IDS, trackIds);
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
deleteTrackDialogFragment.setArguments(bundle);
@@ -66,7 +67,7 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
final Track.Id[] trackIds = (Track.Id[]) getArguments().getParcelableArray(KEY_TRACK_IDS);
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
@@ -85,6 +86,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
*/
public interface ConfirmDeleteCaller {
void onConfirmDeleteDone(long[] trackIds);
void onConfirmDeleteDone(Track.Id... trackIds);
}
}
@@ -26,6 +26,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.DialogUtils;
@@ -41,9 +42,9 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
private DeleteMarkerCaller caller;
public static void showDialog(FragmentManager fragmentManager, long[] markerIds) {
public static void showDialog(FragmentManager fragmentManager, Waypoint.Id... waypointIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_MARKER_IDS, markerIds);
bundle.putParcelableArray(KEY_MARKER_IDS, waypointIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle);
@@ -63,23 +64,23 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
final Waypoint.Id[] waypointIds = (Waypoint.Id[]) getArguments().getParcelableArray(KEY_MARKER_IDS);
final FragmentActivity fragmentActivity = getActivity();
int titleId;
int messageId;
if (markerIds.length == 1 && markerIds[0] == -1L) {
if (waypointIds == null) {
titleId = R.string.generic_delete_all_confirm_title;
messageId = R.string.marker_delete_all_confirm_message;
} else {
titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
titleId = waypointIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = waypointIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
}
return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (long markerId : markerIds) {
contentProviderUtils.deleteWaypoint(getContext(), markerId);
for (Waypoint.Id waypointId : waypointIds) {
contentProviderUtils.deleteWaypoint(getContext(), waypointId);
}
caller.onDeleteMarkerDone();
}).start());
@@ -206,7 +206,7 @@ public class ExportProgressDialogFragment extends DialogFragment {
}
private Boolean exportTrack(Context context, Track track) {
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context);
//TODO Move to helper function
String fileName = track.getId() + "." + trackFileFormat.getExtension();
@@ -218,7 +218,7 @@ public class ExportProgressDialogFragment extends DialogFragment {
}
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
if (trackExporter.writeTrack(context, outputStream)) {
if (trackExporter.writeTrack(track, outputStream)) {
return true;
} else {
if (!file.delete()) {
@@ -205,16 +205,16 @@ public class ImportProgressDialogFragment extends DialogFragment {
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
trackImporter = new GpxFileTrackImporter(context);
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext(), -1L);
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext());
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
trackImporter = new KmzTrackImporter(context, -1L, file.getUri());
trackImporter = new KmzTrackImporter(context, file.getUri());
} else {
Log.i(TAG, "Unsupported file format.");
return false;
}
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
return trackImporter.importFile(inputStream) != -1L;
return trackImporter.importFile(inputStream) != null;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
@@ -93,9 +93,9 @@ public class MarkerDetailFragment extends Fragment {
}
};
public static MarkerDetailFragment newInstance(long markerId) {
public static MarkerDetailFragment newInstance(Waypoint.Id waypointId) {
Bundle bundle = new Bundle();
bundle.putLong(KEY_MARKER_ID, markerId);
bundle.putParcelable(KEY_MARKER_ID, waypointId);
MarkerDetailFragment fragment = new MarkerDetailFragment();
fragment.setArguments(bundle);
@@ -106,8 +106,8 @@ public class MarkerDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long markerId = getArguments().getLong(KEY_MARKER_ID);
if (markerId == -1L) {
Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID);
if (waypointId == null) {
Log.d(TAG, "invalid marker id");
getParentFragmentManager().popBackStack();
return;
@@ -182,7 +182,7 @@ public class MarkerDetailFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
long markerId = getArguments().getLong(KEY_MARKER_ID);
Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID); //TODO Should only happen in onCreate?
FragmentActivity fragmentActivity = getActivity();
Intent intent;
switch (item.getItemId()) {
@@ -191,7 +191,7 @@ public class MarkerDetailFragment extends Fragment {
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
startActivity(intent);
return true;
case R.id.marker_detail_share:
@@ -202,7 +202,7 @@ public class MarkerDetailFragment extends Fragment {
}
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), waypointId);
return true;
default:
return super.onOptionsItemSelected(item);
@@ -216,7 +216,7 @@ public class MarkerDetailFragment extends Fragment {
*/
private void updateWaypoint(boolean refresh) {
if (refresh || waypoint == null) {
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
waypoint = contentProviderUtils.getWaypoint(getArguments().getParcelable(KEY_MARKER_ID)); //TODO Should only happen in onCreate?
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
getParentFragmentManager().popBackStack();
@@ -53,8 +53,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private TrackDataHub trackDataHub;
private Handler handlerUpdateUI;
//TODO Initialize immediately and remove in onDestroy()
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackPoint lastTrackPoint = null;
private TrackStatistics lastTrackStatistics = null;
@@ -77,12 +76,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
if (PreferencesUtils.isKey(getContext(), R.string.recording_track_id_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(getContext());
if (PreferencesUtils.getRecordingTrackId(getContext()) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
if (PreferencesUtils.getRecordingTrackId(getContext()).isValid()) {
// A recording track id has been set -> Resumes track and starts timer.
resumeTrackDataHub();
if (trackRecordingServiceConnection == null) {
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
}
trackRecordingServiceConnection.startConnection(getContext());
handlerUpdateUI.post(updateUIeachSecond);
@@ -222,8 +218,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
super.onResume();
resumeTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
trackRecordingServiceConnection.startConnection(getContext());
handlerUpdateUI.post(updateUIeachSecond);
@@ -241,10 +235,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
@Override
public void onStop() {
super.onStop();
if (trackRecordingServiceConnection != null) {
trackRecordingServiceConnection.unbind(getContext());
}
trackRecordingServiceConnection = null;
trackRecordingServiceConnection.unbind(getContext());
}
@Override
@@ -296,6 +287,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
longitudeValue = null;
}
@Override
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
}
@Override
public void onTrackUpdated(final Track track) {
if (isResumed()) {
@@ -333,9 +330,10 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
lastTrackPoint = null;
}
if (lastTrackPoint != null) {
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy);
TrackPoint trackPoint = lastTrackPoint; //NOTE: There seems to be a race condition; just fix the symptom for now.
if (trackPoint != null) {
boolean hasFix = !LocationUtils.isLocationOld(trackPoint.getLocation());
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy);
if (!hasFix || !hasGoodFix) {
lastTrackPoint = null;
@@ -5,7 +5,6 @@ import android.content.Context;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
@@ -21,8 +20,8 @@ public enum TrackFileFormat {
KML_ONLY_TRACK {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, false, false, false);
}
@Override
@@ -36,8 +35,8 @@ public enum TrackFileFormat {
},
KML_WITH_TRACKDETAIL {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, false, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, false, false);
}
@Override
@@ -51,8 +50,8 @@ public enum TrackFileFormat {
},
KML_WITH_TRACKDETAIL_AND_SENSORDATA {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, false);
}
@Override
@@ -69,12 +68,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, false, false, exportPhotos);
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
@Override
@@ -96,12 +95,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, false, exportPhotos);
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
@Override
@@ -124,8 +123,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, exportPhotos);
}
@Override
@@ -133,8 +132,8 @@ public enum TrackFileFormat {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
public String getExtension() {
@@ -153,8 +152,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = true;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, exportPhotos);
}
@Override
@@ -162,8 +161,8 @@ public enum TrackFileFormat {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, newTrackWriter(context), exportPhotos);
}
public String getExtension() {
@@ -178,7 +177,7 @@ public enum TrackFileFormat {
},
GPX {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
public TrackWriter newTrackWriter(Context context) {
return new GpxTrackWriter(context.getString(R.string.app_name));
}
@@ -196,12 +195,12 @@ public enum TrackFileFormat {
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, boolean exportPhotos) {
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, boolean exportPhotos) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter);
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
return new KmzTrackExporter(context, contentProviderUtils, fileTrackExporter, exportPhotos);
}
/**
@@ -209,19 +208,18 @@ public enum TrackFileFormat {
*/
public abstract String getMimeType();
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
public TrackExporter newTrackExporter(Context context) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
TrackWriter trackWriter = newTrackWriter(context);
return new FileTrackExporter(contentProviderUtils, trackWriter);
}
/**
* Creates a new track writer for the format.
*
* @param context the context
* @param multiple true for writing multiple tracks
* @param context the context
*/
public abstract TrackWriter newTrackWriter(Context context, boolean multiple);
public abstract TrackWriter newTrackWriter(Context context);
/**
* Returns the file extension for each format.
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
@@ -42,7 +41,6 @@ public class FileTrackExporter implements TrackExporter {
private static final String TAG = FileTrackExporter.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils;
private final Track[] tracks;
private final TrackWriter trackWriter;
/**
@@ -50,29 +48,37 @@ public class FileTrackExporter implements TrackExporter {
*
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
* @param tracks the tracks
*/
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks) {
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter) {
this.contentProviderUtils = contentProviderUtils;
this.tracks = tracks;
this.trackWriter = trackWriter;
}
@Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
try {
trackWriter.prepare(outputStream);
trackWriter.writeHeader(tracks);
for (Track track1 : tracks) {
writeWaypoints(track1);
}
trackWriter.writeBeginTracks();
boolean hasMultipleTracks = tracks.length > 1;
if (hasMultipleTracks) {
trackWriter.writeMultiTrackBegin();
}
long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
for (Track track : tracks) {
long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
writeLocations(track, offset);
}
trackWriter.writeEndTracks();
if (hasMultipleTracks) {
trackWriter.writeMultiTrackEnd();
}
trackWriter.writeFooter();
trackWriter.close();
@@ -92,7 +98,7 @@ public class FileTrackExporter implements TrackExporter {
* I am leaving the number of waypoints very high which should not be a problem, because we don't try to load them into objects all at the same time.
*/
boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -121,7 +127,6 @@ public class FileTrackExporter implements TrackExporter {
boolean wroteTrack = false;
boolean wroteSegment = false;
boolean isLastLocationValid = false;
int locationNumber = 0;
TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
@@ -133,7 +138,6 @@ public class FileTrackExporter implements TrackExporter {
TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(trackPoint, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation());
boolean isSegmentValid = isLocationValid && isLastLocationValid;
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.util.StringUtils;
*
* @author Sandor Dornbush
*/
//TODO Export waypoints
public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -93,10 +94,15 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
printWriter.println("xsi:schemaLocation=" +
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">");
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
printWriter.println("<metadata>");
Track track = tracks[0];
@@ -127,27 +133,25 @@ public class GpxTrackWriter implements TrackWriter {
public void writeWaypoint(Waypoint waypoint) {
if (printWriter != null) {
Location location = waypoint.getLocation();
if (location != null) {
printWriter.println("<wpt " + formatLocation(location) + ">");
if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
}
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
printWriter.println("</wpt>");
printWriter.println("<wpt " + formatLocation(location) + ">");
if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
}
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(waypoint.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(waypoint.getCategory()) + "</type>");
printWriter.println("</wpt>");
}
}
@Override
public void writeBeginTracks() {
public void writeMultiTrackBegin() {
// Do nothing
}
@Override
public void writeEndTracks() {
public void writeMultiTrackEnd() {
// Do nothing
}
@@ -158,7 +162,11 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>");
printWriter.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
printWriter.println("<extensions>");
printWriter.println("<topografix:color>c0c0c0</topografix:color>");
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
printWriter.println("</extensions>");
}
}
@@ -57,7 +57,6 @@ public class KmlTrackWriter implements TrackWriter {
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
private final boolean hasMultipleTracks;
private final boolean exportPhotos;
private final boolean exportTrackDetail;
private final boolean exportSensorData;
@@ -74,14 +73,12 @@ public class KmlTrackWriter implements TrackWriter {
/**
* @param context the context
* @param hasMultipleTracks should encode multiple tracks into one file?
* @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
* @param exportSensorData should {@link TrackPoint}'s sensor data be exported?
* @param exportPhotos should pictures be exported (if true: exports to KMZ)?
*/
public KmlTrackWriter(Context context, boolean hasMultipleTracks, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
public KmlTrackWriter(Context context, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
this.context = context;
this.hasMultipleTracks = hasMultipleTracks;
this.exportTrackDetail = exportTrackDetail;
this.exportSensorData = exportSensorData;
this.exportPhotos = exportPhotos;
@@ -108,7 +105,10 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\"");
printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\"");
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\">");
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">");
//TODO ADD xsi:schemaLocation here!
printWriter.println("<Document>");
printWriter.println("<open>1</open>");
printWriter.println("<visibility>1</visibility>");
@@ -116,7 +116,7 @@ public class KmlTrackWriter implements TrackWriter {
if (exportTrackDetail) {
Track track = tracks[0];
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<atom:author><atom:name>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:name></atom:author>");
printWriter.println("<atom:generator>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:generator>");
}
writeTrackStyle();
@@ -175,18 +175,16 @@ public class KmlTrackWriter implements TrackWriter {
}
}
@Override
public void writeBeginTracks() {
if (printWriter != null && hasMultipleTracks) {
public void writeMultiTrackBegin() {
if (printWriter != null) {
printWriter.println("<Folder id=tour>");
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
printWriter.println("<open>1</open>");
}
}
@Override
public void writeEndTracks() {
if (printWriter != null && hasMultipleTracks) {
public void writeMultiTrackEnd() {
if (printWriter != null) {
printWriter.println("</Folder>");
}
}
@@ -204,6 +202,7 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
printWriter.println("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>");
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
}
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
@@ -389,7 +388,7 @@ public class KmlTrackWriter implements TrackWriter {
* @param trackId the track id containing the location
* @param location the location
*/
private float getHeading(long trackId, Location location) {
private float getHeading(Track.Id trackId, Location location) {
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
if (trackPointId == -1L) {
return location.getBearing();
@@ -460,7 +459,7 @@ public class KmlTrackWriter implements TrackWriter {
/**
* Writes a simple array style.
*
* @param name the name of the simple array.
* @param name the name of the simple array.
* @param extendedDataType the extended data display name
*/
private void writeSimpleArrayStyle(String name, String extendedDataType) {
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.util.FileUtils;
* @author Jimmy Shih
*/
public class KmzTrackExporter implements TrackExporter {
private static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_KML_FILE = "doc.kml";
@@ -49,27 +50,32 @@ public class KmzTrackExporter implements TrackExporter {
private static final int BUFFER_SIZE = 4096;
private final ContentProviderUtils contentProviderUtils;
private final TrackExporter fileTrackExporter;
private final Track[] tracks;
private final FileTrackExporter fileTrackExporter;
private final boolean exportPhotos;
private Context context;
/**
* Constructor.
*
* @param context the context
* @param contentProviderUtils the content provider utils
* @param trackExporter the file track exporter
* @param tracks the tracks to export
* @param trackExporter the file track exporter
*/
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, TrackExporter trackExporter, Track[] tracks, boolean exportPhotos) {
public KmzTrackExporter(Context context, ContentProviderUtils contentProviderUtils, FileTrackExporter trackExporter, boolean exportPhotos) {
this.context = context;
this.contentProviderUtils = contentProviderUtils;
this.fileTrackExporter = trackExporter;
this.tracks = tracks;
this.exportPhotos = exportPhotos;
}
@Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(outputStream);
@@ -78,7 +84,7 @@ public class KmzTrackExporter implements TrackExporter {
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
zipOutputStream.putNextEntry(zipEntry);
boolean success = fileTrackExporter.writeTrack(context, zipOutputStream);
boolean success = fileTrackExporter.writeTrack(tracks, zipOutputStream);
zipOutputStream.closeEntry();
if (!success) {
Log.e(TAG, "Unable to write kml in kmz");
@@ -86,7 +92,7 @@ public class KmzTrackExporter implements TrackExporter {
}
// Add photos
if (exportPhotos) addImages(context, zipOutputStream);
if (exportPhotos) addImages(context, tracks ,zipOutputStream);
return true;
} catch (InterruptedException | IOException e) {
Log.e(TAG, "Unable to write track", e);
@@ -102,9 +108,9 @@ public class KmzTrackExporter implements TrackExporter {
}
}
private void addImages(Context context, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
private void addImages(Context context, Track[] tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
for (Track track : tracks) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, -1)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -16,12 +16,12 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import androidx.annotation.NonNull;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
/**
* Track exporting for exporting track to an {@link OutputStream}.
*
@@ -29,12 +29,7 @@ import java.io.OutputStream;
*/
public interface TrackExporter {
/**
* Write track to an output stream.
* Depending on the implementation a context might be required.
*
* @param context the context
* @param outputStream the output stream
*/
boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream);
boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream);
boolean writeTrack(Track track, @NonNull OutputStream outputStream);
}
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* For each waypoint:
* {@link #writeWaypoint(Waypoint)}
* {@link #writeEndWaypoints()}
* {@link #writeBeginTracks()}
* {@link #writeMultiTrackBegin()}
* For each track:
* {@link #writeBeginTrack(Track, TrackPoint)}
* For each segment:
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* {@link #writeTrackPoint(TrackPoint)}
* {@link #writeCloseSegment()}
* {@link #writeEndTrack(Track, TrackPoint)}
* {@link #writeEndTracks()}
* {@link #writeMultiTrackEnd()}
* {@link #writeFooter()}
* {@link #close()}
* </pre>
@@ -96,12 +96,12 @@ public interface TrackWriter {
/**
* Writes the beginning of the tracks.
*/
void writeBeginTracks();
void writeMultiTrackBegin();
/**
* Writes the end of the tracks,
*/
void writeEndTracks();
void writeMultiTrackEnd();
/**
* Writes the beginning of a track.
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.io.file.importer;
import android.content.Context;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;
import android.util.Log;
@@ -62,12 +63,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The maximum number of buffered locations for bulk-insertion
private static final int MAX_BUFFERED_LOCATIONS = 512;
private final Context context;
private final long importTrackId;
private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval;
private final List<Long> trackIds = new ArrayList<>();
private Track.Id importTrackId;
private final List<Track.Id> trackIds = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>();
// The current element content
@@ -96,19 +98,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The SAX locator to get the current line information
private Locator locator;
/**
* @param context the context
* @param importTrackId the track id to import to. -1L to import to a new track.
*/
@Deprecated
// Remove importTrackId
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
}
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils, Track.Id importTrackId) {
this(context, contentProviderUtils);
this.importTrackId = importTrackId;
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
@@ -126,7 +126,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
@Override
public long importFile(InputStream inputStream) {
public Track.Id importFile(InputStream inputStream) {
try {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
long start = System.currentTimeMillis();
@@ -134,15 +134,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
saxParser.parse(inputStream, this);
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
if (trackIds.size() != 1) {
Log.d(TAG, trackIds.size() + " tracks imported");
cleanImport();
return -1L;
throw new MultiTracksImportException();
}
return trackIds.get(0);
} catch (IOException | SAXException | ParserConfigurationException e) {
} catch (MultiTracksImportException | IOException | SAXException | SQLiteConstraintException | ParserConfigurationException e) {
Log.e(TAG, "Unable to import file", e);
cleanImport();
return -1L;
return null;
}
}
@@ -155,7 +153,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (size == 0) {
return;
}
long trackId = trackIds.get(size - 1);
Track.Id trackId = trackIds.get(size - 1);
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
return;
@@ -227,16 +225,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/
protected void onTrackStart() throws SAXException {
trackData = new TrackData();
long trackId;
if (importTrackId == -1L) {
Track.Id trackId;
if (importTrackId == null) {
Uri uri = contentProviderUtils.insertTrack(trackData.track);
trackId = Long.parseLong(uri.getLastPathSegment());
trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
} else {
if (trackIds.size() > 0) {
throw new SAXException(createErrorMessage("Cannot import more than one track to an existing track " + importTrackId));
}
trackId = importTrackId;
contentProviderUtils.clearTrack(context, trackId);
contentProviderUtils.clearTrack(trackId);
}
trackIds.add(trackId);
trackData.track.setId(trackId);
@@ -275,7 +273,18 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.trackStatisticsUpdater.updateTime(trackData.importTime);
}
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(trackData.track);
try {
contentProviderUtils.updateTrack(trackData.track);
} catch (SQLiteConstraintException e) {
if (PreferencesUtils.getPreventReimportTracks(context)) {
throw e;
}
//TODO This is a workaround until we have proper UI.
trackData.track.setUuid(UUID.randomUUID());
contentProviderUtils.updateTrack(trackData.track);
}
}
/**
@@ -390,7 +399,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @param externalPhotoUrl the file name
*/
protected String getInternalPhotoUrl(String externalPhotoUrl) {
if (importTrackId == -1L) {
if (importTrackId == null) {
Log.e(TAG, "Track id is invalid.");
return null;
}
@@ -508,7 +517,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* Cleans up import.
*/
private void cleanImport() {
for (long trackId : trackIds) {
for (Track.Id trackId : trackIds) {
contentProviderUtils.deleteTrack(context, trackId);
}
}
@@ -544,4 +553,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The number of buffered locations
int numBufferedTrackPoints = 0;
}
@Deprecated
//TODO According to the reader AND writer, we support multiple tracks per file; we should implement it here also.
private static class MultiTracksImportException extends RuntimeException {
}
}
@@ -45,6 +45,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_TRACK_SEGMENT = "trkseg";
private static final String TAG_TYPE = "type";
private static final String TAG_WAYPOINT = "wpt";
private static final String TAG_ID = "opentracks:trackid";
private static final String ATTRIBUTE_LAT = "lat";
private static final String ATTRIBUTE_LON = "lon";
@@ -64,7 +65,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
@VisibleForTesting
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
super(context, -1L, contentProviderUtils);
super(context, contentProviderUtils);
}
@Override
@@ -144,6 +145,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
cadence = content.trim();
}
break;
case TAG_ID:
if (content != null) {
uuid = content.trim();
}
}
// Reset element content
@@ -57,6 +57,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_STYLE_URL = "styleUrl";
private static final String TAG_VALUE = "value";
private static final String TAG_WHEN = "when";
private static final String TAG_UUID = "opentracks:trackid";
private static final String ATTRIBUTE_NAME = "name";
@@ -68,23 +69,17 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private ArrayList<Float> heartRateList;
private ArrayList<Float> powerList;
/**
* @param context the context
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
*/
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
public KmlFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, new ContentProviderUtils(context));
public KmlFileTrackImporter(Context context) {
this(context, new ContentProviderUtils(context));
}
@VisibleForTesting
KmlFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
super(context, importTrackId, contentProviderUtils);
KmlFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
super(context, contentProviderUtils);
}
@Override
public void startElement(String uri, String localName, String tag, Attributes attributes)
throws SAXException {
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
switch (tag) {
case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY:
@@ -109,50 +104,70 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
@Override
public void endElement(String uri, String localName, String tag) throws SAXException {
//TODO Check if order is relevant (uses localname and tag); and convert to switch statement
if (tag.equals(TAG_KML)) {
onFileEnd();
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
onWaypointEnd();
} else if (localName.equals(TAG_COORDINATES)) {
onWaypointLocationEnd();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
onTrackEnd();
} else if (tag.equals(TAG_GX_TRACK)) {
onTrackSegmentEnd();
} else if (tag.equals(TAG_GX_COORD)) {
onTrackPointEnd();
} else if (tag.equals(TAG_GX_VALUE)) {
onExtendedDataValueEnd();
} else if (tag.equals(TAG_NAME)) {
if (content != null) {
name = content.trim();
}
} else if (localName.equals(TAG_DESCRIPTION)) {
if (content != null) {
description = content.trim();
}
} else if (localName.equals(TAG_ICON)) {
if (content != null) {
icon = content.trim();
}
} else if (localName.equals(TAG_VALUE)) {
if (content != null) {
category = content.trim();
}
} else if (localName.equals(TAG_WHEN)) {
if (content != null) {
time = content.trim();
}
} else if (localName.equals(TAG_STYLE_URL)) {
if (content != null) {
waypointType = content.trim();
}
} else if (localName.equals(TAG_HREF)) {
if (content != null) {
photoUrl = content.trim();
}
switch (tag) {
case TAG_KML:
onFileEnd();
break;
case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY:
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
onWaypointEnd();
break;
case TAG_COORDINATES:
onWaypointLocationEnd();
break;
case TAG_GX_MULTI_TRACK:
onTrackEnd();
break;
case TAG_GX_TRACK:
onTrackSegmentEnd();
break;
case TAG_GX_COORD:
onTrackPointEnd();
break;
case TAG_GX_VALUE:
onExtendedDataValueEnd();
break;
case TAG_NAME:
if (content != null) {
name = content.trim();
}
break;
case TAG_UUID:
if (content != null) {
uuid = content.trim();
}
break;
case TAG_DESCRIPTION:
if (content != null) {
description = content.trim();
}
break;
case TAG_ICON:
if (content != null) {
icon = content.trim();
}
break;
case TAG_VALUE:
if (content != null) {
category = content.trim();
}
break;
case TAG_WHEN:
if (content != null) {
time = content.trim();
}
break;
case TAG_STYLE_URL:
if (content != null) {
waypointType = content.trim();
}
break;
case TAG_HREF:
if (content != null) {
photoUrl = content.trim();
}
break;
}
// Reset element content
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
@@ -52,34 +53,31 @@ public class KmzTrackImporter implements TrackImporter {
private static final int BUFFER_SIZE = 4096;
private final Context context;
private final long importTrackId;
private Track.Id importTrackId; //TODO needed?
private final Uri uriKmzFile;
/**
* @param context the context
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
* @param uriFile URI of the kmz file.
* @param context the context
* @param uriFile URI of the kmz file.
*/
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
public KmzTrackImporter(Context context, long importTrackId, Uri uriFile) {
public KmzTrackImporter(Context context, Uri uriFile) {
this.context = context;
this.importTrackId = importTrackId;
this.uriKmzFile = uriFile;
}
@Override
public long importFile(InputStream inputStream) {
long trackId;
public Track.Id importFile(InputStream inputStream) {
Track.Id trackId;
if (!copyKmzImages()) {
cleanImport(context, importTrackId);
return -1L;
return null;
}
trackId = findAndParseKmlFile(inputStream);
if (trackId == -1L) {
if (trackId == null) {
cleanImport(context, importTrackId);
return -1L;
return null;
}
deleteOrphanImages(context, trackId);
@@ -94,7 +92,7 @@ public class KmzTrackImporter implements TrackImporter {
*/
private boolean copyKmzImages() {
try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile);
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
@@ -143,7 +141,7 @@ public class KmzTrackImporter implements TrackImporter {
* Returns true if fileName ends with some of the KMZ_IMAGES_EXT suffixes.
* Otherwise returns false.
*/
private boolean hasImageExtension(String fileName) {
private boolean hasImageExtension(String fileName) {
if (fileName == null) {
return false;
}
@@ -158,27 +156,28 @@ public class KmzTrackImporter implements TrackImporter {
/**
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
* TODO: May load multiple tracks, but only returns the last Track.Id.
*
* @param inputStream kmz input stream.
* @return -1 if error or the id of the track otherwise.
* @return null if error or the id of the track otherwise.
*/
private long findAndParseKmlFile(InputStream inputStream) {
private Track.Id findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
long trackId = -1L;
Track.Id trackId = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) {
Log.d(TAG, "Thread interrupted");
return -1L;
return null;
}
String fileName = zipEntry.getName();
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
trackId = parseKml(zipInputStream);
if (trackId == -1L) {
if (trackId != null) {
Log.d(TAG, "Unable to parse kml in kmz");
return -1L;
return null;
}
}
@@ -187,7 +186,7 @@ public class KmzTrackImporter implements TrackImporter {
return trackId;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return -1L;
return null;
}
}
@@ -197,8 +196,8 @@ public class KmzTrackImporter implements TrackImporter {
* @param context the Context object.
* @param trackId the id of the Track.
*/
private void deleteOrphanImages(Context context, long trackId) {
if (trackId != 1L) {
private void deleteOrphanImages(Context context, Track.Id trackId) {
if (!trackId.isValid()) {
// 1.- Gets all photo names in the waypoints of the track identified by id.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Waypoint> waypoints = contentProviderUtils.getWaypoints(trackId);
@@ -230,7 +229,7 @@ public class KmzTrackImporter implements TrackImporter {
*
* @param trackId the trackId
*/
private void cleanImport(Context context, long trackId) {
private void cleanImport(Context context, Track.Id trackId) {
if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteTrack(context, trackId);
@@ -243,8 +242,8 @@ public class KmzTrackImporter implements TrackImporter {
* @param zipInputStream the zip input stream
* @return the imported track id or -1L
*/
private long parseKml(ZipInputStream zipInputStream) throws IOException {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId);
private Track.Id parseKml(ZipInputStream zipInputStream) throws IOException {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
return kmlFileTrackImporter.importFile(byteArrayInputStream);
@@ -274,7 +273,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param fileName the file name
*/
private void readAndSaveImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
if (importTrackId == -1L || fileName.equals("")) {
if (importTrackId == null || fileName.equals("")) {
return;
}
@@ -17,6 +17,8 @@ package de.dennisguse.opentracks.io.file.importer;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
/**
* Interface for a track importer.
*
@@ -28,8 +30,8 @@ public interface TrackImporter {
* Import a file.
*
* @param inputStream the file's input stream
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT.
* @return the imported track id or null.
*/
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
long importFile(InputStream inputStream);
Track.Id importFile(InputStream inputStream);
}
@@ -33,6 +33,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.TaskStackBuilder;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.TrackRecordingActivity;
@@ -43,6 +46,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.handlers.HandlerServer;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
@@ -72,7 +76,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager;
private long recordingTrackId;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private int recordingDistanceInterval;
private int maxRecordingDistance;
@@ -120,6 +124,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private HandlerServer handlerServer;
private List<TrackRecordingServiceCallback> listeners = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
@@ -132,7 +138,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
recordingTrackId = null;
PreferencesUtils.register(this, sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
@@ -152,7 +158,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override
public void onDestroy() {
if (listeners != null) {
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(GpsStatusValue.GPS_NONE);
}
listeners.clear();
listeners = null;
}
handlerServer.stop(this);
handlerServer = null;
if (remoteSensorManager != null) {
remoteSensorManager.stop();
@@ -189,7 +204,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return recordingTrackPaused;
}
public long getRecordingTrackId() {
public Track.Id getRecordingTrackId() {
return recordingTrackId;
}
@@ -215,9 +230,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @return the waypoint id
*/
public long insertWaypoint(String name, String category, String description, String photoUrl) {
public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) {
return -1L;
return null;
}
if (name == null) {
@@ -231,7 +246,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (trackPoint == null) {
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
return -1L;
return null;
}
category = category != null ? category : "";
@@ -246,7 +261,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
return new Waypoint.Id(ContentUris.parseId(uri));
}
/**
@@ -254,10 +269,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @return the track id
*/
long startNewTrack() {
Track.Id startNewTrack() {
if (isRecording()) {
Log.d(TAG, "Ignore startNewTrack. Already recording.");
return -1L;
return null;
}
long now = System.currentTimeMillis();
trackStatisticsUpdater = new TrackStatisticsUpdater(now);
@@ -265,7 +280,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert a track
Track track = new Track();
Uri uri = contentProviderUtils.insertTrack(track);
long trackId = ContentUris.parseId(uri);
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Update shared preferences
updateRecordingState(trackId, false);
@@ -290,7 +305,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @param trackId the id of the track to be resumed.
*/
void resumeTrack(long trackId) {
void resumeTrack(Track.Id trackId) {
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId + " does not exists.");
@@ -318,7 +333,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
if (track == null) {
if (isRecording()) {
Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId);
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
updateRecordingState(null, true);
}
showNotification(false);
return;
@@ -388,10 +403,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
}
// Need to remember the recordingTrackId before setting it to -1L
long trackId = recordingTrackId;
Track.Id trackId = recordingTrackId;
boolean wasPaused = recordingTrackPaused;
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
updateRecordingState(null, true);
// Update database
Track track = contentProviderUtils.getTrack(trackId);
@@ -474,7 +489,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the track id
* @return the location or null
*/
private TrackPoint getLastValidTrackPointInCurrentSegment(long trackId) {
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
if (!currentSegmentHasTrackPoint()) {
return null;
}
@@ -492,9 +507,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the recording track id
* @param paused true if the recording is paused
*/
private void updateRecordingState(long trackId, boolean paused) {
private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, trackId);
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, currentTrackId);
recordingTrackPaused = paused;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
}
@@ -581,6 +597,20 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
lastTrackPoint = trackPoint;
}
@Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
if (listeners != null) {
notificationManager.updateContent(getString(gpsStatusValue.message));
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(gpsStatusValue);
}
}
}
public void addListener(TrackRecordingServiceCallback listener) {
listeners.add(listener);
}
/**
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
*
@@ -679,4 +709,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.remoteSensorManager = remoteSensorManager;
}
public GpsStatusValue getGpsStatus() {
return handlerServer.getGpsStatus();
}
}
@@ -2,8 +2,11 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
/**
@@ -22,6 +25,16 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
this.trackRecordingService = trackRecordingService;
}
@Override
public void addListener(TrackRecordingServiceCallback listener) {
trackRecordingService.addListener(listener);
}
@Override
public GpsStatusValue getGpsStatus() {
return trackRecordingService.getGpsStatus();
}
@Override
public void startGps() {
trackRecordingService.tryStartGps();
@@ -32,12 +45,12 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long startNewTrack() {
public Track.Id startNewTrack() {
return trackRecordingService.startNewTrack();
}
@Override
public void resumeTrack(long trackId) {
public void resumeTrack(Track.Id trackId) {
trackRecordingService.resumeTrack(trackId);
}
@@ -67,7 +80,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long getRecordingTrackId() {
public Track.Id getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId();
}
@@ -77,7 +90,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long insertWaypoint(String name, String category, String description, String photoUrl) {
public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
}
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.services;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
* Interface all activities have to implements to receive information from the service.
*/
public interface TrackRecordingServiceCallback {
void onGpsStatusChange(GpsStatusValue newStatus);
}
@@ -31,6 +31,8 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -177,18 +179,18 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Adds a marker.
*
* @return the id of the marker or -1L if none could be created.
* @return the id of the marker or null if none could be created.
*/
public long addMarker(Context context, String name, String category, String description, String photoUrl) {
public Waypoint.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
try {
long markerId = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (markerId != -1L) {
Waypoint.Id waypoint = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (waypoint != null) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId;
return waypoint;
}
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
@@ -196,7 +198,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
return null;
}
/**
@@ -213,7 +215,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
try {
if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
@@ -17,8 +17,11 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
/**
@@ -42,12 +45,12 @@ public interface TrackRecordingServiceInterface {
*
* @return the track ID of the new track.
*/
long startNewTrack();
Track.Id startNewTrack();
/**
* Resumes the track identified by trackId.
*/
void resumeTrack(long trackId);
void resumeTrack(Track.Id trackId);
/**
* Pauses the current recording track.
@@ -75,13 +78,14 @@ public interface TrackRecordingServiceInterface {
boolean isPaused();
/**
* Gets the current recording track ID. Returns -1 if not recording.
* Gets the current recording track ID. Returns null if not recording.
*/
long getRecordingTrackId();
Track.Id getRecordingTrackId();
/**
* Gets the total time for the current recording track. Returns 0 if not recording.
*/
//TODO milliseconds?
long getTotalTime();
/**
@@ -89,7 +93,7 @@ public interface TrackRecordingServiceInterface {
*
* @return the ID of the inserted waypoint
*/
long insertWaypoint(String name, String category, String description, String photoUrl);
Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl);
/**
* Gets the current sensor data. Returns null if there is no data.
@@ -110,4 +114,8 @@ public interface TrackRecordingServiceInterface {
*/
@VisibleForTesting
void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy);
void addListener(TrackRecordingServiceCallback listener);
GpsStatusValue getGpsStatus();
}
@@ -0,0 +1,232 @@
package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.os.Handler;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* This class handle GPS status according to received locations and some thresholds.
*/
class GpsStatus {
private static final String TAG = GpsStatus.class.getSimpleName();
// The quantity of milliseconds that GpsStatus waits from minimal interval to consider GPS lost.
private static final int SIGNAL_LOST_THRESHOLD = (int) (10 * UnitConversions.S_TO_MS);
// Threshold for accuracy.
private double signalBadThreshold;
// Threshold for time without points.
private int signalLostThreshold;
private GpsStatusValue gpsStatus = GpsStatusValue.GPS_NONE;
private GpsStatusListener client;
private Context context;
// Last location. It can be null.
private Location lastLocation = null;
// The last valid (not null) location. Null value means that there have not been any location yet.
private Location lastValidLocation = null;
// Flag to prevent GpsStatus checks two or more locations at the same time.
private boolean checking = false;
private class GpsStatusRunner implements Runnable {
private boolean stopped = false;
@Override
public void run() {
if (gpsStatus != null && !stopped) {
onLocationChanged(null);
gpsStatusHandler.postDelayed(gpsStatusRunner, getIntervalThreshold());
}
}
public void stop() {
stopped = true;
}
}
private Handler gpsStatusHandler;
private GpsStatusRunner gpsStatusRunner = null;
/**
* @param context The context object.
* @param client The client.
* @param minRecordingInterval Value of min recording interval preference.
*/
public GpsStatus(Context context, GpsStatusListener client, int minRecordingInterval) {
this.client = client;
this.context = context;
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(context);
signalLostThreshold = minRecordingInterval > 0 ? minRecordingInterval * (int) UnitConversions.ONE_SECOND_MS + SIGNAL_LOST_THRESHOLD : SIGNAL_LOST_THRESHOLD;
gpsStatusHandler = new Handler();
}
/**
* The client that uses GpsStatus has to call this method to stop the Runnable if needed.
*/
public void stop() {
client = null;
if (gpsStatusRunner != null) {
gpsStatusRunner.stop();
gpsStatusRunner = null;
}
}
/**
* Method to change the bad threshold from outside.
*
* @param value New preference value to signalBadThreshold.
*/
public void onRecordingDistanceChanged(int value) {
signalBadThreshold = value;
}
/**
* Method to change the lost threshold from outside.
*
* @param value Minimal recording interval preference value in seconds or an special value: -1, -2, 0.
*/
public void onMinRecordingIntervalChanged(int value) {
signalLostThreshold = value > 0 ? value * (int) UnitConversions.ONE_SECOND_MS + SIGNAL_LOST_THRESHOLD : SIGNAL_LOST_THRESHOLD;
}
/**
* This method must be called from the client every time a new location is received.
* Receive new location and calculate the new status if needed.
* It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any.
*/
public void onLocationChanged(final Location location) {
if (checking == true) {
return;
}
checking = true;
if (lastLocation != null) {
checkStatusFromLastLocation();
} else if (lastValidLocation != null) {
checkStatusFromLastValidLocation();
}
if (location != null) {
// Update location's time to the current time millis when location has been received.
location.setTime(System.currentTimeMillis());
lastValidLocation = location;
}
lastLocation = location;
checking = false;
}
/**
* Checks if lastLocation has new GPS status looking up time and accuracy.
* It depends of signalLostThreshold and signalBadThreshold.
* If there is any change then it does the change.
* Also, it'll run the runnable if signal is bad or stop it if the signal is lost.
*/
private void checkStatusFromLastLocation() {
if (System.currentTimeMillis() - lastLocation.getTime() > signalLostThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) {
// Too much time without receiving signal -> signal lost.
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
sendStatus(oldStatus, gpsStatus);
stopStatusRunner();
} else if (lastLocation.getAccuracy() > signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_BAD) {
// Too little accuracy -> bad signal.
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_BAD;
sendStatus(oldStatus, gpsStatus);
startStatusRunner();
} else if (lastLocation.getAccuracy() <= signalBadThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_FIX) {
// Gps okay.
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_FIX;
sendStatus(oldStatus, gpsStatus);
startStatusRunner();
}
}
/**
* Checks if lastValidLocation has a new GPS status looking up time.
* It depends on signalLostThreshold.
* If there is any change then it does the change.
*/
private void checkStatusFromLastValidLocation() {
if (System.currentTimeMillis() - lastValidLocation.getTime() > signalLostThreshold) {
// Too much time without locations -> lost signal? (wait signalLostThreshold from last valid location).
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
sendStatus(oldStatus, gpsStatus);
stopStatusRunner();
lastValidLocation = null;
}
}
/**
* This method must be called from the client every time the GPS sensor is enabled.
* Anyway, it checks that GPS is enabled because the client assumes that if it's on then GPS is enabled but user can disable GPS by hand.
*/
public void onGpsEnabled() {
if (gpsStatus != GpsStatusValue.GPS_ENABLED) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_ENABLED;
sendStatus(oldStatus, gpsStatus);
startStatusRunner();
} else {
onGpsDisabled();
}
}
}
/**
* This method must be called from service every time the GPS sensor is disabled.
*/
public void onGpsDisabled() {
if (gpsStatus != GpsStatusValue.GPS_DISABLED) {
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_DISABLED;
sendStatus(oldStatus, gpsStatus);
lastLocation = null;
lastValidLocation = null;
stopStatusRunner();
}
}
private void sendStatus(GpsStatusValue prev, GpsStatusValue current) {
if (client != null) {
client.onGpsStatusChanged(prev, current);
}
}
private void startStatusRunner() {
if (gpsStatusRunner == null) {
gpsStatusRunner = new GpsStatusRunner();
gpsStatusRunner.run();
}
}
private void stopStatusRunner() {
if (gpsStatusRunner != null) {
gpsStatusRunner.stop();
gpsStatusRunner = null;
}
}
public int getIntervalThreshold() {
return signalLostThreshold;
}
public GpsStatusValue getGpsStatus() {
return gpsStatus;
}
public interface GpsStatusListener {
void onGpsStatusChanged(GpsStatusValue prevStatus, GpsStatusValue currentStatus);
}
}

Some files were not shown because too many files have changed in this diff Show More