Compare commits

...
Author SHA1 Message Date
johan12345 1f2635ba31 upgrade maplibre & car app library 2026-09-05 23:02:48 +02:00
fcf5393cfa Translated using Weblate (Catalan)
Currently translated at 100.0% (3 of 3 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (98 of 98 strings)

Translated using Weblate (Catalan)

Currently translated at 98.1% (374 of 381 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Catalan)

Currently translated at 86.0% (328 of 381 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (3 of 3 strings)

Translated using Weblate (Catalan)

Currently translated at 50.0% (1 of 2 strings)

Translated using Weblate (Catalan)

Currently translated at 66.6% (2 of 3 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (Catalan)

Currently translated at 41.4% (158 of 381 strings)

Added translation using Weblate (Catalan)

Added translation using Weblate (Catalan)

Added translation using Weblate (Catalan)

Added translation using Weblate (Catalan)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joan Montané <jmontane@gmail.com>
Co-authored-by: Johan von Forstner <johan.forstner@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/evmap/android-automotive/ca/
Translate-URL: https://hosted.weblate.org/projects/evmap/android-fdroid/ca/
Translate-URL: https://hosted.weblate.org/projects/evmap/android-google/ca/
Translate-URL: https://hosted.weblate.org/projects/evmap/android/ca/
Translate-URL: https://hosted.weblate.org/projects/evmap/app-store-metadata/ca/
Translation: EVMap/Android
Translation: EVMap/Android (strings specific to F-Droid variant)
Translation: EVMap/Android (strings specific to Google Play variant)
Translation: EVMap/Android (strings specific to the Android Automotive OS app)
Translation: EVMap/App Store metadata
2026-09-05 22:57:03 +02:00
Hosted WeblateandPixelcode 1f9da92465 Translated using Weblate (German)
Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (German)

Currently translated at 100.0% (381 of 381 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Pixelcode <{pixelcode|git}@pgp.forgoodeyesonly.eu>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/de/
Translation: EVMap/Android
2026-09-05 22:57:03 +02:00
johan12345 986c3a2579 disable edge-to-edge on Android 6/7
because light navigation bar is not supported
2026-08-22 16:39:33 +02:00
johan12345 e9240cea0f fix Google Maps legacy renderer 2026-08-22 16:36:18 +02:00
johan12345 2283d21ffa add Let's Encrypt trust stores for Android 6&7 2026-08-22 16:27:15 +02:00
johan12345 92f67d36d4 improve error logging for CacheLiveData 2026-08-22 15:46:28 +02:00
johan12345 5d9b3a4a68 use GMS security provider if available 2026-08-22 15:45:06 +02:00
johan12345 1f6a515480 Release 2.1.4 2026-08-21 13:31:30 +02:00
johan12345 34329e1ecc update MapLibre with Vulkan+OpenGL ES 3.0 support 2026-08-21 13:16:55 +02:00
johan12345 1ce1fd9256 EnBW: move to new host 2026-08-21 12:58:04 +02:00
Hosted Weblateandmcliquid 4495c4fb92 Translated using Weblate (German)
Currently translated at 100.0% (381 of 381 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: mcliquid <info@mcliquid.de>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/de/
Translation: EVMap/Android
2026-08-20 22:59:44 +02:00
johan12345 37ede73295 update Mastodon instance
fixes #444
2026-08-20 21:49:57 +02:00
johan12345 6d351d4fa9 update Google Maps & Places 2026-08-03 22:29:42 +02:00
johan12345 e342f3e424 update AnyMaps & MapLibre
fixes #443
2026-08-03 22:12:54 +02:00
johan12345 305eb5fe65 update Google Play Billing library 2026-08-03 21:51:09 +02:00
johan12345 10053bcc00 update Maplibre 2026-07-11 13:29:53 +02:00
Hosted WeblateandRobert Högberg 87cef478a0 Translated using Weblate (Swedish)
Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (380 of 380 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Robert Högberg <robert.hogberg@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/sv/
Translation: EVMap/Android
2026-07-11 13:29:08 +02:00
Hosted WeblateandThomas Di Cristofaro c8907c8b1c Translated using Weblate (Italian)
Currently translated at 100.0% (379 of 379 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Thomas Di Cristofaro <hostedweblate.8347@tdc.akamail.it>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/it/
Translation: EVMap/Android
2026-07-11 13:29:08 +02:00
Hosted WeblateandFjuro ab5125ede9 Translated using Weblate (Czech)
Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (380 of 380 strings)

Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/cs/
Translation: EVMap/Android
2026-07-11 13:29:07 +02:00
Hosted WeblateandPriit Jõerüüt cb3433828c Translated using Weblate (Estonian)
Currently translated at 100.0% (381 of 381 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (380 of 380 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/et/
Translation: EVMap/Android
2026-07-11 13:29:07 +02:00
dependabot[bot] 3ae8f8ddf0 Bump faraday from 1.10.5 to 1.10.6
Bumps [faraday](https://github.com/lostisland/faraday) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v1.10.5...v1.10.6)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 1.10.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-11 13:26:39 +02:00
dependabot[bot] b9760dafd1 Bump excon from 0.76.0 to 1.5.0
Bumps [excon](https://github.com/excon/excon) from 0.76.0 to 1.5.0.
- [Changelog](https://github.com/excon/excon/blob/master/changelog.txt)
- [Commits](https://github.com/excon/excon/compare/v0.76.0...v1.5.0)

---
updated-dependencies:
- dependency-name: excon
  dependency-version: 1.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-11 13:24:07 +02:00
Robert Högberg 0043f57e2e Add support for Megawatt Charging System (MCS) connector
So far only used by the Nobil data source
2026-06-18 19:10:28 +02:00
johan12345 7488dbf100 upgrade MapLibre 2026-06-04 17:53:04 +02:00
johan12345 89231c99f6 Improve DataSourceSelectFragment UI
Tablet layout: scroll down automatically to reveal "Let's go" button
2026-06-01 19:17:14 +02:00
johan12345 6aeede295e UiSettingsFragment: avoid crash when APP_OPEN_BY_DEFAULT_SETTINGS is not supported 2026-06-01 18:55:56 +02:00
johan12345 9125ff133b Merge branch 'master' of github.com:johan12345/EVMap 2026-06-01 18:52:17 +02:00
Robert Högberg 0cbb2d82c0 Limit download progress percentage to range 0-100
For nobil the percentage reaches 101. I haven't figured out why nobil's data
is out of sync.

When querying nobil for the total number of chargers we get:
Norway: 4957
Sweden: 9108
------------
       14065

.. but the data dump we download has:
Norway: 5168
Sweden: 9059
------------
       14227
2026-06-01 18:44:48 +02:00
Robert Högberg abb00f144b Fix app crash when downloading full charger list from data source
R.string.downloading_chargers_percent expects a Float argument, but an Int
was supplied.
2026-05-31 23:13:37 +02:00
johan12345 e31a83377f upgrade Material Components 2026-05-30 17:30:41 +02:00
johan12345 a0e645886e Tesla: catch and clear invalid refresh tokens 2026-05-30 17:25:31 +02:00
johan12345 b57bb5d884 catch non-IOExceptions in Retrofit interceptors 2026-05-30 17:07:45 +02:00
johan12345 b7571acc9e upgrade ACRA 2026-05-30 14:28:02 +02:00
johan12345 757c8589fb remove old logging call 2026-05-30 14:19:40 +02:00
johan12345 bbebc9fc20 fix typo in README 2026-05-26 00:10:12 +02:00
johan12345 40d894a270 GitHub Actions update 2026-05-26 00:02:53 +02:00
johan12345 88ab03e681 aboutLibraries update 2026-05-25 23:49:25 +02:00
johan12345 6b751a7371 use Gradle configuration cache 2026-05-25 23:39:52 +02:00
johan12345 edd451d61e fix missing view id 2026-05-25 23:34:28 +02:00
johan12345 15e0e6d6ae upgrade some dependencies 2026-05-25 23:33:44 +02:00
johan12345 2e9b7c941b viewbinding migration cleanup 2026-05-25 23:23:04 +02:00
johan12345 dda491c34f migrate from databinding to viewbinding
fixes #144
2026-05-25 17:23:10 +02:00
johan12345 f6c39e1bdc update GitHub Action versions 2026-05-25 13:07:27 +02:00
johan12345 f03dc0ebdb update AGP to 9.2.1 2026-05-25 13:05:06 +02:00
johan12345 640057e0ef remove unused jsonapi-retrofit library
was part of Chargeprice integration
2026-05-25 13:00:16 +02:00
johan12345 4fa4831fcb migrate to libs.versions.toml version catalog 2026-05-25 12:59:31 +02:00
johan12345 bb50f0bcc4 upgrade AGP to 9.0.1 2026-05-25 12:48:28 +02:00
johan12345 637f8dbfad fix insets handling on Android <=10
fixes #423
2026-05-23 01:01:48 +02:00
johan12345 fa21ffd7ff upgrade AnyMaps, MapLibre uses OpenGL ES 3.0 now
#345
2026-05-23 00:45:44 +02:00
johan12345 53809b55fb upgrade Kotlin and KSP, switch to Moshi-IR 2026-05-23 00:32:07 +02:00
johan12345 63a10d7f68 Release 2.1.3 2026-05-10 13:30:24 +02:00
johan12345 e117c844ed Merge branch 'car-app-library-1.8' 2026-05-10 13:25:22 +02:00
johan12345 36174297e5 fix Tesla login
redirect URI changed
fixes #422
2026-05-06 20:35:26 +02:00
99 changed files with 4054 additions and 2375 deletions

No files matched your search

+6 -4
View File
@@ -11,14 +11,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Java environment
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 21
distribution: 'zulu'
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Decrypt keystore
run: openssl aes-256-cbc -K ${{ secrets.encrypted_53968681344a_key }} -iv ${{ secrets.encrypted_53968681344a_iv }} -in _ci/keystore.jks.enc -out _ci/keystore.jks -d
- name: Extract version code
@@ -40,7 +42,7 @@ jobs:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.KEYSTORE_ALIAS_PASSWORD }}
run: ./gradlew exportLibraryDefinitions assembleRelease --no-daemon
run: ./gradlew exportLibraryDefinitions assembleRelease
- name: Export licenses in Appning format
run: python3 _ci/export_licenses_appning.py
+12 -10
View File
@@ -16,26 +16,28 @@ jobs:
buildvariant: [ FossNormal, FossAutomotive, GoogleNormal, GoogleAutomotive ]
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Java environment
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 21
distribution: 'zulu'
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Copy apikeys.xml
run: cp _ci/apikeys-ci.xml app/src/main/res/values/apikeys.xml
- name: Build app
run: ./gradlew assemble${{ matrix.buildvariant }}Debug --no-daemon
run: ./gradlew assemble${{ matrix.buildvariant }}Debug
- name: Run unit tests
run: ./gradlew test${{ matrix.buildvariant }}DebugUnitTest --no-daemon
run: ./gradlew test${{ matrix.buildvariant }}DebugUnitTest
- name: Run Android Lint
run: ./gradlew lint${{ matrix.buildvariant }}Debug --no-daemon
run: ./gradlew lint${{ matrix.buildvariant }}Debug
- name: Check licenses
run: ./gradlew exportLibraryDefinitions --no-daemon
run: ./gradlew exportLibraryDefinitions
apk_check:
name: Release APK checks (${{ matrix.buildvariant }})
@@ -49,10 +51,10 @@ jobs:
run: sudo apt install -y checksec
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Java environment
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: 'zulu'
@@ -62,7 +64,7 @@ jobs:
run: cp _ci/apikeys-ci.xml app/src/main/res/values/apikeys.xml
- name: Build app
run: ./gradlew assemble${{ matrix.buildvariant }}Release --no-daemon
run: ./gradlew assemble${{ matrix.buildvariant }}Release
- name: Unpack native libraries from APK
run: |
+2 -1
View File
@@ -14,4 +14,5 @@ apikeys.xml
/_img/connectors/*.ai
api-7125266970515251116-798419-8e2dda660c80.json
output-metadata.json
licenses_*.csv
licenses_*.csv
gradle/gradle-daemon-jvm.properties
+97 -60
View File
@@ -2,8 +2,10 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
abbrev (0.1.2)
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1196.0)
@@ -26,12 +28,14 @@ GEM
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.3)
base64 (0.3.0)
benchmark (0.5.0)
bigdecimal (4.0.1)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
commander (4.6.0)
highline (~> 2.0.0)
csv (3.3.5)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.1)
@@ -40,8 +44,9 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.0.0)
excon (0.76.0)
faraday (1.10.5)
excon (1.5.0)
logger
faraday (1.10.6)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -66,92 +71,126 @@ GEM
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday-retry (1.0.4)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.156.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
fastlane (2.237.0)
CFPropertyList (>= 2.3, < 5.0.0)
abbrev (~> 0.1)
addressable (>= 2.9.0, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.197)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
base64 (~> 0.2)
benchmark (>= 0.1.0)
bundler (>= 2.4.0, < 5.0.0)
colored (~> 1.2)
commander (~> 4.6)
csv (~> 3.3)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
excon (>= 0.71.0, < 2.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.1.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-env (>= 1.6.0, < 2.3.0)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
jwt (>= 2.10.3, < 4)
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
multi_json (~> 1.12)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3)
naturally (~> 2.2)
nkf (~> 0.2)
optparse (>= 0.1.1, < 1.0.0)
ostruct (>= 0.1.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
security (= 0.1.5)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
terminal-table (~> 3)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.1.0)
gh_inspector (1.1.3)
google-api-client (0.38.0)
google-apis-androidpublisher_v3 (0.104.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
googleauth (~> 1.9)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
retriable (>= 2.0, < 4.a)
google-apis-iamcredentials_v1 (0.28.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.18.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.64.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.9.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.3.3)
faraday (>= 0.17.3, < 2.0)
google-cloud-env (2.2.2)
base64 (~> 0.2)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.0.1)
google-cloud-storage (1.27.0)
addressable (~> 2.5)
google-cloud-storage (1.62.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
google-apis-core (>= 0.18, < 2)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (>= 0.42)
google-cloud-core (~> 1.6)
googleauth (~> 1.9)
mini_mime (~> 1.0)
googleauth (0.13.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
google-logging-utils (0.2.0)
googleauth (1.17.1)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.2)
google-logging-utils (~> 0.1)
jwt (>= 1.4, < 4.0)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
pstore (~> 0.1)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.8)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.3.1)
jwt (2.2.1)
jwt (3.2.0)
base64
logger (1.7.0)
memoist (0.16.2)
mini_magick (4.10.1)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.3.0)
naturally (2.2.0)
nkf (0.3.0)
optparse (0.8.1)
os (1.1.1)
ostruct (0.6.3)
plist (3.5.0)
pstore (0.2.1)
public_suffix (7.0.5)
rake (13.0.1)
representable (3.0.4)
@@ -159,22 +198,20 @@ GEM
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
security (0.1.5)
signet (0.22.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 4.0)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@@ -191,8 +228,8 @@ GEM
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty (0.4.1)
rouge (~> 3.28.0)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
+1 -1
View File
@@ -82,7 +82,7 @@ into new languages.
Sponsors
--------
Many users currently support the development EVMap with their donations. You can find more
Many users currently support the development of EVMap with their donations. You can find more
information on the [Donate page](https://ev-map.app/donate/) on the EVMap website.
<a href="https://www.jawg.io"><img src="https://www.jawg.io/static/Blue@10x-9cdc4596e4e59acbd9ead55e9c28613e.png" alt="JawgMaps" height="38"/></a><br>
+14
View File
@@ -0,0 +1,14 @@
<svg id="vector" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>connector_mcs</title>
<path fill="#000000" d="M7.57,2.904A5.168,5.168 0,0 0,2.4 8.074a5.168,5.168 0,0 0,1.097 3.181l5.656,7.24A3.616,3.616 0,0 0,12 19.885a3.616,3.616 0,0 0,2.843 -1.389l5.656,-7.24 A5.168,5.168 0,0 0,21.6 8.074a5.168,5.168 0,0 0,-5.17 -5.17z" fill-opacity="0" stroke="#000" stroke-width="2" id="path_0"/>
<path d="M11.5 10.159a1.0 1.0 0 0 0 -1.0 1.0 1.0 1.0 0 0 0 1.0 1.0h1.0a1.0 1.0 0 0 0 1.0 -1.0 1.0 1.0 0 0 0 -1.0 -1.0z"/>
<circle fill="none" stroke="black" stroke-width="1px" cx="7.5" cy="8.074" r="2.9" />
<circle fill="none" stroke="black" stroke-width="1px" cx="16.5" cy="8.074" r="2.9" />
<circle cx="9" cy="13" r="1" />
<circle cx="15" cy="13" r="1" />
<circle fill="none" stroke="black" stroke-width="1px" cx="12" cy="15.5" r="1.8" />
</svg>

After

Width:  |  Height:  |  Size: 886 B

+205 -210
View File
@@ -1,14 +1,15 @@
import com.android.build.api.dsl.ApplicationBaseFlavor
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Base64
plugins {
id("com.adarshr.test-logger") version "4.0.0"
id("com.android.application")
id("kotlin-android")
id("kotlin-parcelize")
id("kotlin-kapt")
id("com.google.devtools.ksp").version("2.0.21-1.0.28")
id("androidx.navigation.safeargs.kotlin")
id("com.mikepenz.aboutlibraries.plugin")
alias(libs.plugins.test.logger)
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.ksp)
alias(libs.plugins.moshix)
alias(libs.plugins.navigation.safeargs)
alias(libs.plugins.aboutlibraries)
}
@@ -21,14 +22,12 @@ android {
minSdk = 23
targetSdk = 36
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode = 274
versionName = "2.1.2"
versionCode = 280
versionName = "2.1.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
addApiKeys()
}
val isRunningOnCI = System.getenv("CI") == "true"
@@ -83,6 +82,7 @@ android {
create("google") {
dimension = "dependencies"
versionNameSuffix = "-google"
addGoogleApiKey()
}
create("normal") {
dimension = "automotive"
@@ -102,14 +102,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
buildFeatures {
dataBinding = true
viewBinding = true
buildConfig = true
resValues = true
}
lint {
disable += listOf("NullSafeMutableLiveData")
@@ -127,111 +123,6 @@ android {
namespace = "net.vonforst.evmap"
// add API keys from environment variable if not set in apikeys.xml
applicationVariants.all {
var evmapKey =
System.getenv("EVMAP_API_KEY") ?: project.findProperty("EVMAP_API_KEY")?.toString()
if (evmapKey == null && project.hasProperty("EVMAP_API_KEY_ENCRYPTED")) {
evmapKey = decode(
project.findProperty("EVMAP_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (evmapKey != null) {
resValue("string", "evmap_key", evmapKey)
}
val goingelectricKey =
System.getenv("GOINGELECTRIC_API_KEY") ?: project.findProperty("GOINGELECTRIC_API_KEY")
?.toString()
if (goingelectricKey != null) {
resValue("string", "goingelectric_key", goingelectricKey)
}
var nobilKey =
System.getenv("NOBIL_API_KEY") ?: project.findProperty("NOBIL_API_KEY")?.toString()
if (nobilKey == null && project.hasProperty("NOBIL_API_KEY_ENCRYPTED")) {
nobilKey = decode(
project.findProperty("NOBIL_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (nobilKey != null) {
resValue("string", "nobil_key", nobilKey)
}
var openchargemapKey =
System.getenv("OPENCHARGEMAP_API_KEY") ?: project.findProperty("OPENCHARGEMAP_API_KEY")
?.toString()
if (openchargemapKey == null && project.hasProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED")) {
openchargemapKey = decode(
project.findProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (openchargemapKey != null) {
resValue("string", "openchargemap_key", openchargemapKey)
}
val googleMapsKey =
System.getenv("GOOGLE_MAPS_API_KEY") ?: project.findProperty("GOOGLE_MAPS_API_KEY")
?.toString()
if (googleMapsKey != null && flavorName.startsWith("google")) {
resValue("string", "google_maps_key", googleMapsKey)
}
var mapboxKey =
System.getenv("MAPBOX_API_KEY") ?: project.findProperty("MAPBOX_API_KEY")?.toString()
if (mapboxKey == null && project.hasProperty("MAPBOX_API_KEY_ENCRYPTED")) {
mapboxKey = decode(
project.findProperty("MAPBOX_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (mapboxKey != null) {
resValue("string", "mapbox_key", mapboxKey)
}
var jawgKey =
System.getenv("JAWG_API_KEY") ?: project.findProperty("JAWG_API_KEY")?.toString()
if (jawgKey == null && project.hasProperty("JAWG_API_KEY_ENCRYPTED")) {
jawgKey = decode(
project.findProperty("JAWG_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (jawgKey != null) {
resValue("string", "jawg_key", jawgKey)
}
var arcgisKey =
System.getenv("ARCGIS_API_KEY") ?: project.findProperty("ARCGIS_API_KEY")?.toString()
if (arcgisKey == null && project.hasProperty("ARCGIS_API_KEY_ENCRYPTED")) {
arcgisKey = decode(
project.findProperty("ARCGIS_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (arcgisKey != null) {
resValue("string", "arcgis_key", jawgKey)
}
var fronyxKey =
System.getenv("FRONYX_API_KEY") ?: project.findProperty("FRONYX_API_KEY")?.toString()
if (fronyxKey == null && project.hasProperty("FRONYX_API_KEY_ENCRYPTED")) {
fronyxKey = decode(
project.findProperty("FRONYX_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (fronyxKey != null) {
resValue("string", "fronyx_key", fronyxKey)
}
var acraKey = System.getenv("ACRA_CRASHREPORT_CREDENTIALS")
?: project.findProperty("ACRA_CRASHREPORT_CREDENTIALS")?.toString()
if (acraKey == null && project.hasProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED")) {
acraKey = decode(
project.findProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (acraKey != null) {
resValue("string", "acra_credentials", acraKey)
}
}
packaging {
jniLibs {
pickFirsts.addAll(
@@ -246,6 +137,126 @@ android {
}
}
/**
* add API keys from environment variable if not set in apikeys.xml
*/
fun ApplicationBaseFlavor.addApiKeys() {
var evmapKey =
System.getenv("EVMAP_API_KEY") ?: project.findProperty("EVMAP_API_KEY")?.toString()
if (evmapKey == null && project.hasProperty("EVMAP_API_KEY_ENCRYPTED")) {
evmapKey = decode(
project.findProperty("EVMAP_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (evmapKey != null) {
resValue("string", "evmap_key", evmapKey)
}
val goingelectricKey =
System.getenv("GOINGELECTRIC_API_KEY") ?: project.findProperty("GOINGELECTRIC_API_KEY")
?.toString()
if (goingelectricKey != null) {
resValue("string", "goingelectric_key", goingelectricKey)
}
var nobilKey =
System.getenv("NOBIL_API_KEY") ?: project.findProperty("NOBIL_API_KEY")?.toString()
if (nobilKey == null && project.hasProperty("NOBIL_API_KEY_ENCRYPTED")) {
nobilKey = decode(
project.findProperty("NOBIL_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (nobilKey != null) {
resValue("string", "nobil_key", nobilKey)
}
var openchargemapKey =
System.getenv("OPENCHARGEMAP_API_KEY") ?: project.findProperty("OPENCHARGEMAP_API_KEY")
?.toString()
if (openchargemapKey == null && project.hasProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED")) {
openchargemapKey = decode(
project.findProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (openchargemapKey != null) {
resValue("string", "openchargemap_key", openchargemapKey)
}
var mapboxKey =
System.getenv("MAPBOX_API_KEY") ?: project.findProperty("MAPBOX_API_KEY")?.toString()
if (mapboxKey == null && project.hasProperty("MAPBOX_API_KEY_ENCRYPTED")) {
mapboxKey = decode(
project.findProperty("MAPBOX_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (mapboxKey != null) {
resValue("string", "mapbox_key", mapboxKey)
}
var jawgKey =
System.getenv("JAWG_API_KEY") ?: project.findProperty("JAWG_API_KEY")?.toString()
if (jawgKey == null && project.hasProperty("JAWG_API_KEY_ENCRYPTED")) {
jawgKey = decode(
project.findProperty("JAWG_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (jawgKey != null) {
resValue("string", "jawg_key", jawgKey)
}
var arcgisKey =
System.getenv("ARCGIS_API_KEY") ?: project.findProperty("ARCGIS_API_KEY")?.toString()
if (arcgisKey == null && project.hasProperty("ARCGIS_API_KEY_ENCRYPTED")) {
arcgisKey = decode(
project.findProperty("ARCGIS_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (arcgisKey != null) {
resValue("string", "arcgis_key", arcgisKey)
}
var fronyxKey =
System.getenv("FRONYX_API_KEY") ?: project.findProperty("FRONYX_API_KEY")?.toString()
if (fronyxKey == null && project.hasProperty("FRONYX_API_KEY_ENCRYPTED")) {
fronyxKey = decode(
project.findProperty("FRONYX_API_KEY_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (fronyxKey != null) {
resValue("string", "fronyx_key", fronyxKey)
}
var acraKey = System.getenv("ACRA_CRASHREPORT_CREDENTIALS")
?: project.findProperty("ACRA_CRASHREPORT_CREDENTIALS")?.toString()
if (acraKey == null && project.hasProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED")) {
acraKey = decode(
project.findProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED").toString(),
"FmK.d,-f*p+rD+WK!eds"
)
}
if (acraKey != null) {
resValue("string", "acra_credentials", acraKey)
}
}
fun ApplicationBaseFlavor.addGoogleApiKey() {
val googleMapsKey =
System.getenv("GOOGLE_MAPS_API_KEY") ?: project.findProperty("GOOGLE_MAPS_API_KEY")
?.toString()
if (googleMapsKey != null) {
resValue("string", "google_maps_key", googleMapsKey)
}
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
}
}
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
androidComponents {
beforeVariants { variantBuilder ->
if (variantBuilder.buildType == "releaseAutomotivePackageName"
@@ -286,126 +297,110 @@ aboutLibraries {
}
dependencies {
val kotlinVersion: String by rootProject.extra
val aboutLibsVersion: String by rootProject.extra
val navVersion: String by rootProject.extra
val normalImplementation by configurations
val googleImplementation by configurations
val automotiveImplementation by configurations
val fossImplementation by configurations
val testGoogleImplementation by configurations
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.core:core-splashscreen:1.2.0")
implementation("androidx.activity:activity-ktx:1.11.0")
implementation("androidx.fragment:fragment-ktx:1.8.9")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("com.google.android.material:material:1.13.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.recyclerview:recyclerview:1.4.0")
implementation("androidx.browser:browser:1.9.0")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.viewpager2:viewpager2:1.1.0")
implementation("androidx.security:security-crypto:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.10.5")
implementation("com.github.ev-map:CustomBottomSheetBehavior:e48f73ea7b")
implementation("com.squareup.retrofit2:retrofit:3.0.0")
implementation("com.squareup.retrofit2:converter-moshi:3.0.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:okhttp-urlconnection:4.12.0")
implementation("com.squareup.moshi:moshi-kotlin:1.15.2")
implementation("com.squareup.moshi:moshi-adapters:1.15.2")
implementation("com.markomilos.jsonapi:jsonapi-retrofit:1.1.0")
implementation("io.coil-kt:coil:2.7.0")
implementation("com.github.ev-map:StfalconImageViewer:c6ba219517")
implementation("com.mikepenz:aboutlibraries-core:$aboutLibsVersion")
implementation("com.mikepenz:aboutlibraries:$aboutLibsVersion")
implementation("com.airbnb.android:lottie:6.6.10")
implementation("io.michaelrocks.bimap:bimap:1.1.0")
implementation("com.github.pengrad:mapscaleview:1.6.0")
implementation("com.github.romandanylyk:PageIndicatorView:b1bad589b5")
implementation("com.github.ev-map:locale-config-x:58b036abf4")
implementation(libs.kotlin.stdlib.jdk7)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.cardview)
implementation(libs.androidx.preference.ktx)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.recyclerview)
implementation(libs.androidx.browser)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.androidx.viewpager2)
implementation(libs.androidx.security.crypto)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.custom.bottom.sheet.behavior)
implementation(libs.retrofit)
implementation(libs.retrofit.converter.moshi)
implementation(libs.okhttp)
implementation(libs.okhttp.urlconnection)
implementation(libs.moshi.kotlin)
implementation(libs.moshi.adapters)
implementation(libs.coil)
implementation(libs.stfalcon.image.viewer)
implementation(libs.aboutlibraries.core)
implementation(libs.aboutlibraries)
implementation(libs.lottie)
implementation(libs.bimap)
implementation(libs.mapscaleview)
implementation(libs.page.indicator.view)
implementation(libs.locale.config.x)
// Android Auto
val carAppVersion = "1.8.0-beta01"
implementation("androidx.car.app:app:$carAppVersion")
normalImplementation("androidx.car.app:app-projected:$carAppVersion")
automotiveImplementation("androidx.car.app:app-automotive:$carAppVersion")
implementation(libs.androidx.car.app)
normalImplementation(libs.androidx.car.app.projected)
automotiveImplementation(libs.androidx.car.app.automotive)
// AnyMaps
val anyMapsVersion = "65e06c4c9a"
implementation("com.github.ev-map.AnyMaps:anymaps-base:$anyMapsVersion")
googleImplementation("com.github.ev-map.AnyMaps:anymaps-google:$anyMapsVersion")
googleImplementation("com.google.android.gms:play-services-maps:19.2.0")
implementation("com.github.ev-map.AnyMaps:anymaps-maplibre:$anyMapsVersion") {
// duplicates classes from mapbox-sdk-services
exclude("org.maplibre.gl", "android-sdk-geojson")
}
implementation("org.maplibre.gl:android-sdk:10.3.5") {
exclude("org.maplibre.gl", "android-sdk-geojson")
implementation(libs.anymaps.base)
googleImplementation(libs.anymaps.google)
googleImplementation(libs.play.services.maps)
implementation(libs.anymaps.maplibre) {
// exclude default (Vulkan) version and use Vulkan + OpenGL ES 3.0 version for better compatibility
exclude("org.maplibre.gl", "android-sdk")
}
implementation(libs.maplibre.android.sdk.vulkan.opengl)
// Google Places
googleImplementation("com.google.android.libraries.places:places:3.5.0")
googleImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.2")
googleImplementation(libs.places)
googleImplementation(libs.kotlinx.coroutines.play.services)
// Mapbox Geocoding
implementation("com.mapbox.mapboxsdk:mapbox-sdk-services:5.8.0")
implementation(libs.mapbox.sdk.services)
// navigation library
implementation("androidx.navigation:navigation-fragment-ktx:$navVersion")
implementation("androidx.navigation:navigation-ui-ktx:$navVersion")
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
// viewmodel library
val lifecycleVersion = "2.9.2"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.livedata.ktx)
// room library
val roomVersion = "2.7.2"
implementation("androidx.room:room-runtime:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
implementation("com.github.anboralabs:spatia-room:1.0.1") {
implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
implementation(libs.spatia.room) {
exclude("com.github.dalgarins", "android-spatialite")
}
// forked version with upgraded libxml 2.15.2 & APP_STL := c++_static
implementation("io.github.ev-map:android-spatialite:2.3.0-alpha")
implementation(libs.android.spatialite)
// billing library
val billingVersion = "7.0.0"
googleImplementation("com.android.billingclient:billing:$billingVersion")
googleImplementation("com.android.billingclient:billing-ktx:$billingVersion")
googleImplementation(libs.billing)
googleImplementation(libs.billing.ktx)
// ACRA (crash reporting)
val acraVersion = "5.12.0"
implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-dialog:$acraVersion")
implementation("ch.acra:acra-limiter:$acraVersion")
implementation(libs.acra.http)
implementation(libs.acra.dialog)
implementation(libs.acra.limiter)
// debug tools
debugImplementation("com.jakewharton.timber:timber:5.0.1")
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
debugImplementation(libs.timber)
debugImplementation(libs.leakcanary.android)
// testing
testImplementation("junit:junit:4.13.2")
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
testImplementation(libs.junit4)
testImplementation(libs.okhttp.mockwebserver)
//noinspection GradleDependency
testImplementation("org.robolectric:robolectric:4.16")
testImplementation("androidx.test:core:1.7.0")
testImplementation("androidx.arch.core:core-testing:2.2.0")
testImplementation("androidx.car.app:app-testing:$carAppVersion")
testImplementation(libs.robolectric)
testImplementation(libs.androidx.test.core)
testImplementation(libs.androidx.arch.core.testing)
testImplementation(libs.androidx.car.app.testing)
androidTestImplementation("androidx.test.ext:junit:1.3.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
androidTestImplementation("androidx.arch.core:core-testing:2.2.0")
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.arch.core.testing)
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
coreLibraryDesugaring(libs.desugar.jdk.libs)
}
fun decode(s: String, key: String): String {
@@ -0,0 +1,938 @@
{
"formatVersion": 1,
"database": {
"version": 29,
"identityHash": "84f71cce385c444726ba336834ddf6b4",
"entities": [
{
"tableName": "ChargeLocation",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `dataSource` TEXT NOT NULL, `name` TEXT NOT NULL, `coordinates` BLOB NOT NULL, `chargepoints` TEXT NOT NULL, `network` TEXT, `dataSourceUrl` TEXT NOT NULL, `url` TEXT, `editUrl` TEXT, `verified` INTEGER NOT NULL, `barrierFree` INTEGER, `operator` TEXT, `generalInformation` TEXT, `amenities` TEXT, `locationDescription` TEXT, `photos` TEXT, `chargecards` TEXT, `accessibility` TEXT, `license` TEXT, `networkUrl` TEXT, `chargerUrl` TEXT, `timeRetrieved` INTEGER NOT NULL, `isDetailed` INTEGER NOT NULL, `coordinatesProjected` BLOB NOT NULL, `city` TEXT, `country` TEXT, `postcode` TEXT, `street` TEXT, `fault_report_created` INTEGER, `fault_report_description` TEXT, `twentyfourSeven` INTEGER, `description` TEXT, `mostart` TEXT, `moend` TEXT, `tustart` TEXT, `tuend` TEXT, `westart` TEXT, `weend` TEXT, `thstart` TEXT, `thend` TEXT, `frstart` TEXT, `frend` TEXT, `sastart` TEXT, `saend` TEXT, `sustart` TEXT, `suend` TEXT, `hostart` TEXT, `hoend` TEXT, `freecharging` INTEGER, `freeparking` INTEGER, `descriptionShort` TEXT, `descriptionLong` TEXT, `chargepricecountry` TEXT, `chargepricenetwork` TEXT, `chargepriceplugTypes` TEXT, PRIMARY KEY(`id`, `dataSource`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "coordinates",
"columnName": "coordinates",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "chargepoints",
"columnName": "chargepoints",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "network",
"columnName": "network",
"affinity": "TEXT"
},
{
"fieldPath": "dataSourceUrl",
"columnName": "dataSourceUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT"
},
{
"fieldPath": "editUrl",
"columnName": "editUrl",
"affinity": "TEXT"
},
{
"fieldPath": "verified",
"columnName": "verified",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "barrierFree",
"columnName": "barrierFree",
"affinity": "INTEGER"
},
{
"fieldPath": "operator",
"columnName": "operator",
"affinity": "TEXT"
},
{
"fieldPath": "generalInformation",
"columnName": "generalInformation",
"affinity": "TEXT"
},
{
"fieldPath": "amenities",
"columnName": "amenities",
"affinity": "TEXT"
},
{
"fieldPath": "locationDescription",
"columnName": "locationDescription",
"affinity": "TEXT"
},
{
"fieldPath": "photos",
"columnName": "photos",
"affinity": "TEXT"
},
{
"fieldPath": "chargecards",
"columnName": "chargecards",
"affinity": "TEXT"
},
{
"fieldPath": "accessibility",
"columnName": "accessibility",
"affinity": "TEXT"
},
{
"fieldPath": "license",
"columnName": "license",
"affinity": "TEXT"
},
{
"fieldPath": "networkUrl",
"columnName": "networkUrl",
"affinity": "TEXT"
},
{
"fieldPath": "chargerUrl",
"columnName": "chargerUrl",
"affinity": "TEXT"
},
{
"fieldPath": "timeRetrieved",
"columnName": "timeRetrieved",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isDetailed",
"columnName": "isDetailed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "coordinatesProjected",
"columnName": "coordinatesProjected",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "address.city",
"columnName": "city",
"affinity": "TEXT"
},
{
"fieldPath": "address.country",
"columnName": "country",
"affinity": "TEXT"
},
{
"fieldPath": "address.postcode",
"columnName": "postcode",
"affinity": "TEXT"
},
{
"fieldPath": "address.street",
"columnName": "street",
"affinity": "TEXT"
},
{
"fieldPath": "faultReport.created",
"columnName": "fault_report_created",
"affinity": "INTEGER"
},
{
"fieldPath": "faultReport.description",
"columnName": "fault_report_description",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.twentyfourSeven",
"columnName": "twentyfourSeven",
"affinity": "INTEGER"
},
{
"fieldPath": "openinghours.description",
"columnName": "description",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.monday.start",
"columnName": "mostart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.monday.end",
"columnName": "moend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.tuesday.start",
"columnName": "tustart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.tuesday.end",
"columnName": "tuend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.wednesday.start",
"columnName": "westart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.wednesday.end",
"columnName": "weend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.thursday.start",
"columnName": "thstart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.thursday.end",
"columnName": "thend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.friday.start",
"columnName": "frstart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.friday.end",
"columnName": "frend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.saturday.start",
"columnName": "sastart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.saturday.end",
"columnName": "saend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.sunday.start",
"columnName": "sustart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.sunday.end",
"columnName": "suend",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.holiday.start",
"columnName": "hostart",
"affinity": "TEXT"
},
{
"fieldPath": "openinghours.days.holiday.end",
"columnName": "hoend",
"affinity": "TEXT"
},
{
"fieldPath": "cost.freecharging",
"columnName": "freecharging",
"affinity": "INTEGER"
},
{
"fieldPath": "cost.freeparking",
"columnName": "freeparking",
"affinity": "INTEGER"
},
{
"fieldPath": "cost.descriptionShort",
"columnName": "descriptionShort",
"affinity": "TEXT"
},
{
"fieldPath": "cost.descriptionLong",
"columnName": "descriptionLong",
"affinity": "TEXT"
},
{
"fieldPath": "chargepriceData.country",
"columnName": "chargepricecountry",
"affinity": "TEXT"
},
{
"fieldPath": "chargepriceData.network",
"columnName": "chargepricenetwork",
"affinity": "TEXT"
},
{
"fieldPath": "chargepriceData.plugTypes",
"columnName": "chargepriceplugTypes",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"dataSource"
]
}
},
{
"tableName": "Favorite",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`favoriteId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chargerId` INTEGER NOT NULL, `chargerDataSource` TEXT NOT NULL, FOREIGN KEY(`chargerId`, `chargerDataSource`) REFERENCES `ChargeLocation`(`id`, `dataSource`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "favoriteId",
"columnName": "favoriteId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chargerId",
"columnName": "chargerId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "chargerDataSource",
"columnName": "chargerDataSource",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"favoriteId"
]
},
"indices": [
{
"name": "index_Favorite_chargerId_chargerDataSource",
"unique": false,
"columnNames": [
"chargerId",
"chargerDataSource"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Favorite_chargerId_chargerDataSource` ON `${TABLE_NAME}` (`chargerId`, `chargerDataSource`)"
}
],
"foreignKeys": [
{
"table": "ChargeLocation",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"chargerId",
"chargerDataSource"
],
"referencedColumns": [
"id",
"dataSource"
]
}
]
},
{
"tableName": "BooleanFilterValue",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` INTEGER NOT NULL, `dataSource` TEXT NOT NULL, `profile` INTEGER NOT NULL, PRIMARY KEY(`key`, `profile`, `dataSource`), FOREIGN KEY(`profile`, `dataSource`) REFERENCES `FilterProfile`(`id`, `dataSource`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profile",
"columnName": "profile",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"key",
"profile",
"dataSource"
]
},
"indices": [
{
"name": "index_BooleanFilterValue_profile_dataSource",
"unique": false,
"columnNames": [
"profile",
"dataSource"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_BooleanFilterValue_profile_dataSource` ON `${TABLE_NAME}` (`profile`, `dataSource`)"
}
],
"foreignKeys": [
{
"table": "FilterProfile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profile",
"dataSource"
],
"referencedColumns": [
"id",
"dataSource"
]
}
]
},
{
"tableName": "MultipleChoiceFilterValue",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `values` TEXT NOT NULL, `all` INTEGER NOT NULL, `dataSource` TEXT NOT NULL, `profile` INTEGER NOT NULL, PRIMARY KEY(`key`, `profile`, `dataSource`), FOREIGN KEY(`profile`, `dataSource`) REFERENCES `FilterProfile`(`id`, `dataSource`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "values",
"columnName": "values",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "all",
"columnName": "all",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profile",
"columnName": "profile",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"key",
"profile",
"dataSource"
]
},
"indices": [
{
"name": "index_MultipleChoiceFilterValue_profile_dataSource",
"unique": false,
"columnNames": [
"profile",
"dataSource"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_MultipleChoiceFilterValue_profile_dataSource` ON `${TABLE_NAME}` (`profile`, `dataSource`)"
}
],
"foreignKeys": [
{
"table": "FilterProfile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profile",
"dataSource"
],
"referencedColumns": [
"id",
"dataSource"
]
}
]
},
{
"tableName": "SliderFilterValue",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` INTEGER NOT NULL, `dataSource` TEXT NOT NULL, `profile` INTEGER NOT NULL, PRIMARY KEY(`key`, `profile`, `dataSource`), FOREIGN KEY(`profile`, `dataSource`) REFERENCES `FilterProfile`(`id`, `dataSource`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "profile",
"columnName": "profile",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"key",
"profile",
"dataSource"
]
},
"indices": [
{
"name": "index_SliderFilterValue_profile_dataSource",
"unique": false,
"columnNames": [
"profile",
"dataSource"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_SliderFilterValue_profile_dataSource` ON `${TABLE_NAME}` (`profile`, `dataSource`)"
}
],
"foreignKeys": [
{
"table": "FilterProfile",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"profile",
"dataSource"
],
"referencedColumns": [
"id",
"dataSource"
]
}
]
},
{
"tableName": "FilterProfile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `dataSource` TEXT NOT NULL, `id` INTEGER NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`dataSource`, `id`))",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "order",
"columnName": "order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"dataSource",
"id"
]
},
"indices": [
{
"name": "index_FilterProfile_dataSource_name",
"unique": true,
"columnNames": [
"dataSource",
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_FilterProfile_dataSource_name` ON `${TABLE_NAME}` (`dataSource`, `name`)"
}
]
},
{
"tableName": "RecentAutocompletePlace",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `dataSource` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `primaryText` TEXT NOT NULL, `secondaryText` TEXT NOT NULL, `latLng` TEXT NOT NULL, `viewport` TEXT, `types` TEXT NOT NULL, PRIMARY KEY(`id`, `dataSource`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "primaryText",
"columnName": "primaryText",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "secondaryText",
"columnName": "secondaryText",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "latLng",
"columnName": "latLng",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "viewport",
"columnName": "viewport",
"affinity": "TEXT"
},
{
"fieldPath": "types",
"columnName": "types",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"dataSource"
]
}
},
{
"tableName": "GEPlug",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"name"
]
}
},
{
"tableName": "GENetwork",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"name"
]
}
},
{
"tableName": "GEChargeCard",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "OCMConnectionType",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `formalName` TEXT, `discontinued` INTEGER, `obsolete` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formalName",
"columnName": "formalName",
"affinity": "TEXT"
},
{
"fieldPath": "discontinued",
"columnName": "discontinued",
"affinity": "INTEGER"
},
{
"fieldPath": "obsolete",
"columnName": "obsolete",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "OCMCountry",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `isoCode` TEXT NOT NULL, `continentCode` TEXT, `title` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isoCode",
"columnName": "isoCode",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "continentCode",
"columnName": "continentCode",
"affinity": "TEXT"
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "OCMOperator",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websiteUrl` TEXT, `title` TEXT NOT NULL, `contactEmail` TEXT, `contactTelephone1` TEXT, `contactTelephone2` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "websiteUrl",
"columnName": "websiteUrl",
"affinity": "TEXT"
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "contactEmail",
"columnName": "contactEmail",
"affinity": "TEXT"
},
{
"fieldPath": "contactTelephone1",
"columnName": "contactTelephone1",
"affinity": "TEXT"
},
{
"fieldPath": "contactTelephone2",
"columnName": "contactTelephone2",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "OSMNetwork",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))",
"fields": [
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"name"
]
}
},
{
"tableName": "SavedRegion",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`region` BLOB NOT NULL, `dataSource` TEXT NOT NULL, `timeRetrieved` INTEGER NOT NULL, `filters` TEXT, `isDetailed` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "region",
"columnName": "region",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "dataSource",
"columnName": "dataSource",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timeRetrieved",
"columnName": "timeRetrieved",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "filters",
"columnName": "filters",
"affinity": "TEXT"
},
{
"fieldPath": "isDetailed",
"columnName": "isDetailed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_SavedRegion_filters_dataSource",
"unique": false,
"columnNames": [
"filters",
"dataSource"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_SavedRegion_filters_dataSource` ON `${TABLE_NAME}` (`filters`, `dataSource`)"
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '84f71cce385c444726ba336834ddf6b4')"
]
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="grant_on_phone">Permet</string>
<string name="auto_location_permission_needed">Per a fer funcionar EVMap en el vostre cotxe, heu de concedir accés a la vostre ubicació.</string>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="donate_paypal">Fes un donatiu amb PayPal</string>
<string name="data_sources_hint">Les dades cartogràfiques de l\'aplicació són proporcionades per OpenStreetMap.</string>
<string name="donations_info" formatted="false">Us ha semblat útil l\'EVMap? Doneu-nos suport per al seu desenvolupament enviant un donatiu al desenvolupador.</string>
</resources>
@@ -6,11 +6,26 @@ import android.util.Log
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.maps.MapsInitializer
import com.google.android.gms.security.ProviderInstaller
import com.google.android.libraries.places.api.Places
fun init(context: Context) {
Places.initialize(context, context.getString(R.string.google_maps_key))
MapsInitializer.initialize(context, MapsInitializer.Renderer.LATEST, null)
ProviderInstaller.installIfNeededAsync(
context,
object : ProviderInstaller.ProviderInstallListener {
override fun onProviderInstalled() {
Log.d("EVMap", "Google Play Security provider installed")
}
override fun onProviderInstallFailed(
errorCode: Int,
recoveryIntent: android.content.Intent?
) {
Log.e("EVMap", "Google Play Security provider installation failed: $errorCode")
}
})
}
fun checkPlayServices(activity: Activity): Boolean {
@@ -1,8 +1,25 @@
package net.vonforst.evmap.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.databinding.ItemDonationBinding
import net.vonforst.evmap.viewmodel.DonationItem
class DonationAdapter() : DataBindingAdapter<DonationItem>() {
class DonationAdapter : DataBindingAdapter<DonationItem>() {
override fun getItemViewType(position: Int): Int = R.layout.item_donation
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = ItemDonationBinding.inflate(inflater, parent, false)
override fun bind(holder: ViewHolder, item: DonationItem) {
super.bind(holder, item)
val binding = holder.binding as ItemDonationBinding
binding.txtName.text = item.product.title
binding.txtPrice.text = item.product.oneTimePurchaseOfferDetails?.formattedPrice
}
}
@@ -19,7 +19,6 @@ import com.google.android.libraries.places.api.net.FetchPlaceRequest
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest
import com.google.android.libraries.places.api.net.PlacesStatusCodes
import kotlinx.coroutines.tasks.await
import net.vonforst.evmap.R
import java.io.IOException
import java.util.concurrent.ExecutionException
import kotlin.math.sqrt
@@ -53,7 +52,7 @@ class GooglePlacesAutocompleteProvider(val context: Context) : AutocompleteProvi
it.getSecondaryText(bold),
it.placeId,
it.distanceMeters?.toDouble(),
it.placeTypes.map { AutocompletePlaceType.valueOf(it.name) })
it.types.mapNotNull { AutocompletePlaceType.valueOfOrNull(it.uppercase()) })
}
} catch (e: ExecutionException) {
val cause = e.cause
@@ -75,12 +74,13 @@ class GooglePlacesAutocompleteProvider(val context: Context) : AutocompleteProvi
override suspend fun getDetails(id: String): PlaceWithBounds {
val request =
FetchPlaceRequest.builder(id, listOf(Place.Field.LAT_LNG, Place.Field.VIEWPORT)).build()
FetchPlaceRequest.builder(id, listOf(Place.Field.LOCATION, Place.Field.VIEWPORT))
.build()
try {
val place = client.fetchPlace(request).await().place
token = AutocompleteSessionToken.newInstance()
return PlaceWithBounds(
AnyMapAdapter.adapt(place.latLng),
AnyMapAdapter.adapt(place.location),
AnyMapAdapter.adapt(place.viewport)
)
} catch (e: ApiException) {
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
@@ -21,7 +20,9 @@ import net.vonforst.evmap.adapter.SingleViewAdapter
import net.vonforst.evmap.databinding.FragmentDonateBinding
import net.vonforst.evmap.databinding.FragmentDonateHeaderBinding
import net.vonforst.evmap.databinding.FragmentDonateReferralBinding
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.viewmodel.DonateViewModel
import net.vonforst.evmap.viewmodel.Status
class DonateFragment : DonateFragmentBase() {
private lateinit var binding: FragmentDonateBinding
@@ -40,9 +41,7 @@ class DonateFragment : DonateFragmentBase() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_donate, container, false)
binding.lifecycleOwner = this
binding.vm = vm
binding = FragmentDonateBinding.inflate(inflater, container, false)
header = FragmentDonateHeaderBinding.inflate(inflater, container, false)
referrals = FragmentDonateReferralBinding.inflate(inflater, container, false)
@@ -75,6 +74,7 @@ class DonateFragment : DonateFragmentBase() {
vm.products.observe(viewLifecycleOwner) {
donationAdapter.submitList(it.data)
binding.progressBar.visibility = goneUnless(it.status == Status.LOADING)
}
vm.purchaseSuccessful.observe(viewLifecycleOwner) {
@@ -4,8 +4,18 @@ import android.app.Activity
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.ProductType
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.PendingPurchasesParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import net.vonforst.evmap.BuildConfig
import net.vonforst.evmap.adapter.Equatable
@@ -13,7 +23,7 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
PurchasesUpdatedListener {
private var billingClient = BillingClient.newBuilder(application)
.setListener(this)
.enablePendingPurchases()
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
.build()
val products: MutableLiveData<Resource<List<DonationItem>>> by lazy {
@@ -67,6 +77,7 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
billingClient.queryProductDetailsAsync(params) { result, details ->
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
products.postValue(Resource.success(details
.productDetailsList
.sortedBy { it.oneTimePurchaseOfferDetails!!.priceAmountMicros }
.map { DonationItem(it) }
))
+15 -30
View File
@@ -1,23 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="net.vonforst.evmap.viewmodel.DonateViewModel" />
<import type="net.vonforst.evmap.viewmodel.Status" />
<variable
name="vm"
type="DonateViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_container"
@@ -46,15 +33,13 @@
tools:itemCount="1"
tools:listitem="@layout/fragment_donate_preview" />
<ProgressBar
android:id="@+id/progressBar3"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/products_list"
app:goneUnless="@{vm.products.status == Status.LOADING}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/products_list" />
</androidx.constraintlayout.widget.ConstraintLayout>
+11 -25
View File
@@ -1,21 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="net.vonforst.evmap.viewmodel.DonationItem" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -24,32 +15,27 @@
android:padding="16dp">
<TextView
android:id="@+id/textView15"
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{item.product.title}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textView21"
app:layout_constraintEnd_toStartOf="@+id/txtPrice"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Spende (extrem langer Beschreibungstext)" />
<TextView
android:id="@+id/textView21"
android:id="@+id/txtPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{item.product.oneTimePurchaseOfferDetails.formattedPrice}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?android:textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1,00 €" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
</androidx.cardview.widget.CardView>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="donations_info" formatted="false">Us ha semblat útil l\'EVMap? Doneu suport al seu desenvolupament enviant un donatiu al desenvolupador.\n\nGoogle es queda un 15% en cada donatiu.</string>
<string name="data_sources_hint">A la configuració també podeu canviar entre Google Maps i OpenStreetMap per a les dades del mapa.</string>
</resources>
+7 -1
View File
@@ -46,7 +46,13 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config">
<!-- required for Google Maps SDK legacy renderer (only on old devices that don't support the new renderer) -->
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<meta-data
android:name="com.mapbox.ACCESS_TOKEN"
@@ -58,7 +58,10 @@ class MapsActivity : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
WindowCompat.enableEdgeToEdge(window)
if (Build.VERSION.SDK_INT >= 26) {
// do not use edgeToEdge on Android API 24-25, because light navigation bar is not supported
WindowCompat.enableEdgeToEdge(window)
}
setContentView(R.layout.activity_maps)
@@ -1,62 +1,73 @@
package net.vonforst.evmap.adapter
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import net.vonforst.evmap.BR
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
import net.vonforst.evmap.api.iconForPlugType
import net.vonforst.evmap.databinding.DialogConnectorDetailsItemBinding
import net.vonforst.evmap.databinding.ItemConnectorBinding
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.ui.availabilityColor
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.goneUnless
import java.time.Instant
import java.util.Locale
interface Equatable {
override fun equals(other: Any?): Boolean
}
abstract class DataBindingAdapter<T : Equatable>(getKey: ((T) -> Any)? = null) :
ListAdapter<T, DataBindingAdapter.ViewHolder<T>>(DiffCallback(getKey)) {
ListAdapter<T, DataBindingAdapter.ViewHolder>(DiffCallback(getKey)) {
var onClickListener: ((T) -> Unit)? = null
var onLongClickListener: ((T) -> Boolean)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<T> {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding =
DataBindingUtil.inflate<ViewDataBinding>(layoutInflater, viewType, parent, false)
return ViewHolder(binding)
return ViewHolder(createBinding(layoutInflater, parent, viewType))
}
override fun onBindViewHolder(holder: ViewHolder<T>, position: Int) =
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
bind(holder, getItem(position))
class ViewHolder<T>(val binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root) {
}
protected abstract fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding
open fun bind(holder: ViewHolder<T>, item: T) {
holder.binding.setVariable(BR.item, item)
holder.binding.executePendingBindings()
class ViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)
open fun bind(holder: ViewHolder, item: T) {
if (onClickListener != null) {
holder.binding.root.setOnClickListener {
val listener = onClickListener ?: return@setOnClickListener
listener(item)
}
} else {
holder.binding.root.setOnClickListener(null)
}
if (onLongClickListener != null) {
holder.binding.root.setOnLongClickListener {
val listener = onLongClickListener ?: return@setOnLongClickListener false
return@setOnLongClickListener listener(item)
}
} else {
holder.binding.root.setOnLongClickListener(null)
}
}
class DiffCallback<T : Equatable>(val getKey: ((T) -> Any)?) : DiffUtil.ItemCallback<T>() {
class DiffCallback<T : Equatable>(private val getKey: ((T) -> Any)?) :
DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = if (getKey != null) {
(getKey)(oldItem) == (getKey)(newItem)
getKey(oldItem) == getKey(newItem)
} else {
oldItem === newItem
}
@@ -79,10 +90,46 @@ class ConnectorAdapter : DataBindingAdapter<ConnectorAdapter.ChargepointWithAvai
data class ChargepointWithAvailability(
val chargepoint: Chargepoint,
val status: List<ChargepointStatus>?
) :
Equatable
) : Equatable
override fun getItemViewType(position: Int): Int = R.layout.item_connector
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = ItemConnectorBinding.inflate(inflater, parent, false)
override fun bind(holder: ViewHolder, item: ChargepointWithAvailability) {
super.bind(holder, item)
val binding = holder.binding as ItemConnectorBinding
val context = binding.root.context
val locale = Locale.getDefault()
binding.imageView.setImageResource(iconForPlugType(item.chargepoint.type))
binding.imageView.contentDescription = item.chargepoint.type
binding.imageView.imageTintList =
ColorStateList.valueOf(availabilityColor(item.status, context))
binding.txtNumber.text = String.format(locale, "x %d", item.chargepoint.count)
binding.txtNumber.visibility = goneUnless(item.status == null)
if (item.status != null) {
binding.txtStatus.text = String.format(
locale,
"%s/%d",
availabilityText(item.status),
item.chargepoint.count
)
binding.txtStatus.backgroundTintList =
ColorStateList.valueOf(availabilityColor(item.status, context))
}
binding.txtStatus.visibility = goneUnless(item.status != null)
binding.txtPower.text = item.chargepoint.formatPower(locale)
binding.txtPower.visibility = goneUnless(item.chargepoint.hasKnownPower())
binding.txtPower.setTextColor(availabilityColor(item.status, context))
}
}
class ConnectorDetailsAdapter : DataBindingAdapter<ConnectorDetailsAdapter.ConnectorDetails>() {
@@ -91,8 +138,32 @@ class ConnectorDetailsAdapter : DataBindingAdapter<ConnectorDetailsAdapter.Conne
val evseId: String?,
val label: String?,
val lastChange: Instant?
) :
Equatable
) : Equatable
override fun getItemViewType(position: Int): Int = R.layout.dialog_connector_details_item
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = DialogConnectorDetailsItemBinding.inflate(inflater, parent, false)
override fun bind(holder: ViewHolder, item: ConnectorDetails) {
super.bind(holder, item)
val binding = holder.binding as DialogConnectorDetailsItemBinding
val context = binding.root.context
binding.imageView.imageTintList =
ColorStateList.valueOf(availabilityColor(item.status, context))
binding.imageView.contentDescription = availabilityText(item.status, null, context)
binding.txtEvseid.text = when {
item.label != null && item.evseId != null -> "${item.label} · ${item.evseId}"
item.label != null -> item.label
else -> item.evseId ?: ""
}
binding.txtStatus.text = availabilityText(item.status, item.lastChange, context)
binding.txtStatus.visibility = goneUnless(item.status != null)
}
}
@@ -4,25 +4,36 @@ import android.content.Context
import android.graphics.Typeface
import android.text.Spannable
import android.text.style.StyleSpan
import android.text.util.Linkify
import android.transition.TransitionManager
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.core.text.buildSpannedString
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.tesla.Pricing
import net.vonforst.evmap.api.availability.tesla.Rates
import net.vonforst.evmap.bold
import net.vonforst.evmap.databinding.ItemDetailBinding
import net.vonforst.evmap.databinding.ItemDetailOpeninghoursBinding
import net.vonforst.evmap.joinToSpannedString
import net.vonforst.evmap.model.ChargeCard
import net.vonforst.evmap.model.ChargeCardId
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Coordinate
import net.vonforst.evmap.model.OpeningHoursDays
import net.vonforst.evmap.plus
import net.vonforst.evmap.ui.applySelectableItemBackground
import net.vonforst.evmap.ui.currency
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.ui.setLinkify
import net.vonforst.evmap.utils.formatDMS
import net.vonforst.evmap.utils.formatDecimal
import java.time.DayOfWeek
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.format.TextStyle
import java.util.Locale
class DetailsAdapter : DataBindingAdapter<DetailsAdapter.Detail>() {
data class Detail(
@@ -37,12 +48,132 @@ class DetailsAdapter : DataBindingAdapter<DetailsAdapter.Detail>() {
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
if (item.hoursDays != null) {
return R.layout.item_detail_openinghours
return if (item.hoursDays != null) {
R.layout.item_detail_openinghours
} else {
return R.layout.item_detail
R.layout.item_detail
}
}
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = when (viewType) {
R.layout.item_detail_openinghours -> ItemDetailOpeninghoursBinding.inflate(
inflater,
parent,
false
)
else -> ItemDetailBinding.inflate(inflater, parent, false)
}
override fun bind(holder: ViewHolder, item: Detail) {
super.bind(holder, item)
if (holder.binding is ItemDetailBinding) {
bindNormal(holder.binding, item)
} else {
bindOpeningHours(holder.binding as ItemDetailOpeninghoursBinding, item)
}
}
private fun bindNormal(binding: ItemDetailBinding, item: Detail) {
binding.root.isClickable = item.clickable
applySelectableItemBackground(binding.root, item.clickable)
binding.imgIcon.setImageResource(item.icon)
binding.imgIcon.contentDescription =
binding.root.context.getString(item.contentDescription)
binding.txtTariff.text = item.text
binding.txtProvider.text = item.detailText
binding.txtProvider.visibility = goneUnless(item.detailText != null)
setLinkify(
binding.txtProvider,
if (item.links) Linkify.WEB_URLS or Linkify.PHONE_NUMBERS else 0,
item.detailText
)
}
private fun bindOpeningHours(binding: ItemDetailOpeninghoursBinding, item: Detail) {
binding.root.isClickable = item.clickable
applySelectableItemBackground(binding.root, item.clickable)
binding.imageView3.setImageResource(item.icon)
binding.imageView3.contentDescription =
binding.root.context.getString(item.contentDescription)
binding.txtTariff.text = item.text
binding.txtProvider.text = item.detailText
binding.txtProvider.visibility = goneUnless(item.detailText != null)
setLinkify(
binding.txtProvider,
if (item.links) Linkify.WEB_URLS or Linkify.PHONE_NUMBERS else 0,
item.detailText
)
val toggleMarginTop = binding.root.resources.getDimensionPixelSize(
if (item.detailText != null) {
R.dimen.expand_toggle_padding_large
} else {
R.dimen.expand_toggle_padding_small
}
)
val toggleLayoutParams = binding.expandToggle.layoutParams
if (toggleLayoutParams is ViewGroup.MarginLayoutParams) {
toggleLayoutParams.topMargin = toggleMarginTop
binding.expandToggle.layoutParams = toggleLayoutParams
}
val days = item.hoursDays
val expanded = days != null
binding.expandToggle.visibility =
if (expanded) android.view.View.VISIBLE else android.view.View.GONE
if (!expanded) return
bindDay(binding.hoursMon, DayOfWeek.MONDAY, days)
bindDay(binding.hoursTue, DayOfWeek.TUESDAY, days)
bindDay(binding.hoursWed, DayOfWeek.WEDNESDAY, days)
bindDay(binding.hoursThu, DayOfWeek.THURSDAY, days)
bindDay(binding.hoursFri, DayOfWeek.FRIDAY, days)
bindDay(binding.hoursSat, DayOfWeek.SATURDAY, days)
bindDay(binding.hoursSun, DayOfWeek.SUNDAY, days)
bindDay(binding.hoursHoliday, null, days)
val toggleListener = android.view.View.OnClickListener {
TransitionManager.beginDelayedTransition(binding.container)
val show = binding.expandToggle.isChecked
val visibility = if (show) android.view.View.VISIBLE else android.view.View.GONE
listOf(
binding.hoursMon.root,
binding.hoursTue.root,
binding.hoursWed.root,
binding.hoursThu.root,
binding.hoursFri.root,
binding.hoursSat.root,
binding.hoursSun.root,
binding.hoursHoliday.root
).forEach { it.visibility = visibility }
}
binding.expandToggle.setOnClickListener(toggleListener)
binding.expandToggle.isChecked = false
toggleListener.onClick(binding.expandToggle)
}
private fun bindDay(
include: net.vonforst.evmap.databinding.ItemDetailOpeninghoursItemBinding,
dayOfWeek: DayOfWeek?,
hours: OpeningHoursDays
) {
include.txtDay.text = if (dayOfWeek != null) {
dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())
} else {
include.root.context.getString(R.string.holiday)
}
include.txtHours.text = hours.getHoursForDayOfWeek(dayOfWeek)?.toString()
?: include.root.context.getString(R.string.closed_unfmt)
}
}
fun buildDetails(
@@ -278,7 +409,9 @@ fun formatChargeCards(
.mapNotNull {
val name = chargecardData[it.id]?.name ?: return@mapNotNull null
if (filteredChargeCards?.contains(it.id) == true) {
name.bold()
buildSpannedString {
append(name, StyleSpan(Typeface.BOLD), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
} else {
name
}
@@ -288,4 +421,4 @@ fun formatChargeCards(
}
return result
}
}
@@ -1,10 +1,22 @@
package net.vonforst.evmap.adapter
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.databinding.ItemFavoriteBinding
import net.vonforst.evmap.ui.availabilityColor
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.distance
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.ui.invisibleUnless
import net.vonforst.evmap.viewmodel.FavoritesViewModel
import net.vonforst.evmap.viewmodel.Status
import java.util.Locale
class FavoritesAdapter(val onDelete: (FavoritesViewModel.FavoritesListItem) -> Unit) :
DataBindingAdapter<FavoritesViewModel.FavoritesListItem>() {
@@ -14,16 +26,49 @@ class FavoritesAdapter(val onDelete: (FavoritesViewModel.FavoritesListItem) -> U
override fun getItemViewType(position: Int): Int = R.layout.item_favorite
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = ItemFavoriteBinding.inflate(inflater, parent, false)
override fun getItemId(position: Int): Long = getItem(position).fav.favorite.favoriteId
@SuppressLint("ClickableViewAccessibility")
override fun bind(
holder: ViewHolder<FavoritesViewModel.FavoritesListItem>,
holder: ViewHolder,
item: FavoritesViewModel.FavoritesListItem
) {
super.bind(holder, item)
val binding = holder.binding as ItemFavoriteBinding
val context = binding.root.context
val locale = Locale.getDefault()
binding.txtName.text = item.charger.name
binding.txtAddress.text = item.charger.address?.toString()
binding.txtAddress.visibility = invisibleUnless(item.charger.address != null)
binding.txtConnectors.text =
item.charger.formatChargepoints(context.stringProvider(), locale)
binding.txtDistance.text = distance(item.distance, context)
binding.txtDistance.visibility = goneUnless(item.distance != null)
val availableData = item.available.data
if (availableData != null) {
binding.txtStatus.text = String.format(
locale,
"%s/%d",
availabilityText(availableData),
item.total
)
binding.txtStatus.backgroundTintList =
ColorStateList.valueOf(availabilityColor(availableData, context))
}
binding.txtStatus.visibility = invisibleUnless(item.available.status == Status.SUCCESS)
binding.progressBar.visibility = goneUnless(item.available.status == Status.LOADING)
binding.foreground.translationX = 0f
binding.btnDelete.setOnClickListener {
binding.foreground.animate()
@@ -1,9 +1,12 @@
package net.vonforst.evmap.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.databinding.ItemFilterProfileBinding
import net.vonforst.evmap.storage.FilterProfile
@@ -17,14 +20,21 @@ class FilterProfilesAdapter(
setHasStableIds(true)
}
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = ItemFilterProfileBinding.inflate(inflater, parent, false)
@SuppressLint("ClickableViewAccessibility")
override fun bind(
holder: ViewHolder<FilterProfile>,
holder: ViewHolder,
item: FilterProfile
) {
super.bind(holder, item)
val binding = holder.binding as ItemFilterProfileBinding
binding.txtName.text = item.name
binding.handle.setOnTouchListener { _, event ->
if (event?.action == MotionEvent.ACTION_DOWN) {
dragHelper.startDrag(holder)
@@ -2,17 +2,27 @@ package net.vonforst.evmap.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.databinding.Observable
import androidx.viewbinding.ViewBinding
import com.google.android.material.chip.Chip
import net.vonforst.evmap.BR
import net.vonforst.evmap.R
import net.vonforst.evmap.databinding.ItemFilterBooleanBinding
import net.vonforst.evmap.databinding.ItemFilterMultipleChoiceBinding
import net.vonforst.evmap.databinding.ItemFilterMultipleChoiceLargeBinding
import net.vonforst.evmap.databinding.ItemFilterSliderBinding
import net.vonforst.evmap.fragment.MultiSelectDialog
import net.vonforst.evmap.model.*
import net.vonforst.evmap.model.BooleanFilter
import net.vonforst.evmap.model.BooleanFilterValue
import net.vonforst.evmap.model.FilterValue
import net.vonforst.evmap.model.FilterWithValue
import net.vonforst.evmap.model.MultipleChoiceFilter
import net.vonforst.evmap.model.MultipleChoiceFilterValue
import net.vonforst.evmap.model.SliderFilter
import net.vonforst.evmap.model.SliderFilterValue
import kotlin.math.max
class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
@@ -36,8 +46,30 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
is SliderFilter -> R.layout.item_filter_slider
}
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = when (viewType) {
R.layout.item_filter_boolean -> ItemFilterBooleanBinding.inflate(inflater, parent, false)
R.layout.item_filter_multiple_choice -> ItemFilterMultipleChoiceBinding.inflate(
inflater,
parent,
false
)
R.layout.item_filter_multiple_choice_large -> ItemFilterMultipleChoiceLargeBinding.inflate(
inflater,
parent,
false
)
R.layout.item_filter_slider -> ItemFilterSliderBinding.inflate(inflater, parent, false)
else -> error("Unknown viewType: $viewType")
}
override fun bind(
holder: ViewHolder<FilterWithValue<FilterValue>>,
holder: ViewHolder,
item: FilterWithValue<FilterValue>
) {
super.bind(holder, item)
@@ -45,7 +77,8 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
is SliderFilterValue -> {
setupSlider(
holder.binding as ItemFilterSliderBinding,
item.filter as SliderFilter, item.value
item.filter as SliderFilter,
item.value
)
}
is MultipleChoiceFilterValue -> {
@@ -53,54 +86,75 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
if (filter.manyChoices) {
setupMultipleChoiceMany(
holder.binding as ItemFilterMultipleChoiceLargeBinding,
filter, item.value
filter,
item.value
)
} else {
setupMultipleChoice(
holder.binding as ItemFilterMultipleChoiceBinding,
filter, item.value
filter,
item.value
)
}
}
is BooleanFilterValue -> {
setupBoolean(holder.binding as ItemFilterBooleanBinding, item)
}
}
}
private fun setupBoolean(
binding: ItemFilterBooleanBinding,
item: FilterWithValue<FilterValue>
) {
val filter = item.filter as BooleanFilter
val value = item.value as BooleanFilterValue
binding.txtName.text = filter.name
binding.toggle.setOnCheckedChangeListener(null)
binding.toggle.isChecked = value.value
binding.toggle.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
value.value = isChecked
}
}
private fun setupMultipleChoice(
binding: ItemFilterMultipleChoiceBinding,
filter: MultipleChoiceFilter,
value: MultipleChoiceFilterValue
) {
// TODO: this implementation seems to be buggy
val inflater = LayoutInflater.from(binding.root.context)
binding.txtName.text = filter.name
value.values.toList().forEach {
// delete values that cannot be selected anymore
if (it !in filter.choices.keys) value.values.remove(it)
}
var showingAll = false
fun updateButtons() {
value.all = value.values == filter.choices.keys
binding.btnAll.isEnabled = !value.all
binding.btnNone.isEnabled = value.values.isNotEmpty()
binding.chipMore.text = binding.root.context.getString(
if (showingAll) R.string.show_less else R.string.show_more
)
}
val chips = mutableMapOf<String, Chip>()
// reuse existing chips in layout
val reuseChips = binding.chipGroup.children.filter {
it.id != R.id.chipMore
}.toMutableList()
binding.chipGroup.children.forEach {
binding.chipGroup.children.toList().forEach {
if (it.id != R.id.chipMore) binding.chipGroup.removeView(it)
}
filter.choices.entries.sortedByDescending {
it.key in value.values
}.sortedByDescending {
if (filter.commonChoices != null) it.key in filter.commonChoices else false
}.forEach { choice ->
var reused = false
val chip = if (reuseChips.size > 0) {
val chip = if (reuseChips.isNotEmpty()) {
reused = true
reuseChips.removeAt(0) as Chip
} else {
@@ -111,6 +165,7 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
) as Chip
}
chip.text = choice.value
chip.setOnCheckedChangeListener(null)
chip.isChecked = choice.key in value.values || value.all
if (value.all && choice.key !in value.values) value.values.add(choice.key)
@@ -124,7 +179,7 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
}
if (filter.commonChoices != null && choice.key !in filter.commonChoices
&& !(chip.isChecked && !value.all) && !binding.showingAll
&& !(chip.isChecked && !value.all) && !showingAll
) {
chip.visibility = View.GONE
} else {
@@ -134,7 +189,7 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
if (!reused) binding.chipGroup.addView(chip, binding.chipGroup.childCount - 1)
chips[choice.key] = chip
}
// delete surplus reusable chips
reuseChips.forEach {
binding.chipGroup.removeView(it)
}
@@ -152,16 +207,17 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
updateButtons()
}
binding.chipMore.setOnClickListener {
binding.showingAll = !binding.showingAll
showingAll = !showingAll
chips.forEach { (key, chip) ->
if (filter.commonChoices != null && key !in filter.commonChoices
&& !(chip.isChecked && !value.all) && !binding.showingAll
&& !(chip.isChecked && !value.all) && !showingAll
) {
chip.visibility = View.GONE
} else {
chip.visibility = View.VISIBLE
}
}
updateButtons()
}
updateButtons()
}
@@ -173,21 +229,36 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
) {
if (value.all) {
value.values = filter.choices.keys.toMutableSet()
binding.notifyPropertyChanged(BR.item)
}
binding.txtName.text = filter.name
binding.txtDetail.text = binding.root.context.getString(
if (value.all) {
R.string.all_selected
} else {
R.string.number_selected
},
value.values.size
)
binding.btnEdit.setOnClickListener {
val dialog =
MultiSelectDialog.getInstance(
filter.name,
filter.choices,
value.values,
commonChoices = filter.commonChoices
)
val dialog = MultiSelectDialog.getInstance(
filter.name,
filter.choices,
value.values,
commonChoices = filter.commonChoices
)
dialog.okListener = { selected ->
value.values = selected.toMutableSet()
value.all = value.values == filter.choices.keys
binding.item = binding.item
binding.txtDetail.text = binding.root.context.getString(
if (value.all) {
R.string.all_selected
} else {
R.string.number_selected
},
value.values.size
)
}
dialog.show((binding.root.context as AppCompatActivity).supportFragmentManager, null)
}
@@ -198,24 +269,37 @@ class FiltersAdapter : DataBindingAdapter<FilterWithValue<FilterValue>>() {
filter: SliderFilter,
value: SliderFilterValue
) {
binding.progress =
max(filter.inverseMapping(value.value) - filter.min, 0)
binding.mappedValue = filter.mapping(binding.progress + filter.min)
binding.txtName.text = filter.name
val progress = max(filter.inverseMapping(value.value) - filter.min, 0)
binding.seekBar.max = filter.max - filter.min
binding.seekBar.setOnSeekBarChangeListener(null)
binding.seekBar.progress = progress
updateSliderValueText(binding, filter, progress)
binding.addOnPropertyChangedCallback(object :
Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
when (propertyId) {
BR.progress -> {
val mapped = filter.mapping(binding.progress + filter.min)
value.value = mapped
binding.mappedValue = mapped
}
}
binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
value.value = filter.mapping(progress + filter.min)
updateSliderValueText(binding, filter, progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
override fun onStopTrackingTouch(seekBar: SeekBar?) = Unit
})
}
private fun updateSliderValueText(
binding: ItemFilterSliderBinding,
filter: SliderFilter,
progress: Int
) {
val mappedValue = filter.mapping(progress + filter.min)
binding.txtValue.text = if (filter.unit.isNullOrBlank()) {
mappedValue.toString()
} else {
"$mappedValue ${filter.unit}"
}
}
override fun getItemId(position: Int): Long {
val key = getItem(position).filter.key
var value = itemids[key]
@@ -1,23 +1,34 @@
package net.vonforst.evmap.adapter
import android.content.Context
import android.content.res.ColorStateList
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.databinding.DataBindingUtil
import android.widget.BaseAdapter
import android.widget.Filter
import android.widget.Filterable
import android.widget.ImageView
import androidx.lifecycle.LiveData
import com.car2go.maps.model.LatLng
import net.vonforst.evmap.R
import net.vonforst.evmap.autocomplete.*
import net.vonforst.evmap.autocomplete.ApiUnavailableException
import net.vonforst.evmap.autocomplete.AutocompletePlace
import net.vonforst.evmap.autocomplete.AutocompletePlaceType
import net.vonforst.evmap.autocomplete.AutocompleteProvider
import net.vonforst.evmap.autocomplete.MapboxAutocompleteProvider
import net.vonforst.evmap.autocomplete.PlaceWithBounds
import net.vonforst.evmap.autocomplete.getAutocompleteProviders
import net.vonforst.evmap.containsAny
import net.vonforst.evmap.databinding.ItemAutocompleteResultBinding
import net.vonforst.evmap.isDarkMode
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.storage.RecentAutocompletePlace
import net.vonforst.evmap.ui.distance
import net.vonforst.evmap.ui.goneUnless
import java.time.Instant
class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatLng>) :
@@ -56,9 +67,8 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
if (getItemViewType(position) == typeItem) {
val viewHolder: ViewHolder
if (view == null) {
val binding: ItemAutocompleteResultBinding = DataBindingUtil.inflate(
val binding = ItemAutocompleteResultBinding.inflate(
LayoutInflater.from(context),
R.layout.item_autocomplete_result,
parent,
false
)
@@ -88,7 +98,27 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
viewHolder: ViewHolder,
place: AutocompletePlace
) {
viewHolder.binding.item = place
val binding = viewHolder.binding
val isSpecial = isSpecialPlace(place.types)
binding.title.text = place.primaryText
binding.subtitle.text = place.secondaryText
binding.icon.contentDescription = place.secondaryText
binding.icon.setImageResource(iconForPlaceType(place.types))
val tintColor = if (isSpecial) {
androidx.appcompat.R.attr.colorPrimary
} else {
androidx.appcompat.R.attr.colorControlNormal
}
val ta = binding.root.context.theme.obtainStyledAttributes(intArrayOf(tintColor))
val tint = ta.getColor(0, 0)
ta.recycle()
binding.icon.imageTintList = ColorStateList.valueOf(tint)
binding.icon.backgroundTintList = ColorStateList.valueOf(tint)
binding.textView16.text = distance(place.distanceMeters, binding.root.context)
binding.textView16.visibility = goneUnless(place.distanceMeters != null)
}
override fun getFilter(): Filter {
@@ -2,10 +2,27 @@ package net.vonforst.evmap.api
import okhttp3.Interceptor
import okhttp3.Response
import okio.IOException
import retrofit2.HttpException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.TimeSource
fun Interceptor.Chain.proceedSafely(request: okhttp3.Request): Response {
return try {
proceed(request)
} catch (e: Throwable) {
if (e is IOException) {
throw e
} else if (e is HttpException) {
// wrap HttpExceptions as IOExceptions, so that they are properly handled by Retrofit's error handling
throw IOException("${e.response()}", e)
} else {
// wrap other exceptions as IOExceptions, so that they are properly handled by Retrofit's error handling
throw IOException(e)
}
}
}
class RateLimitInterceptor : Interceptor {
private val rateLimiter = SimpleRateLimiter(3.0)
@@ -16,7 +33,7 @@ class RateLimitInterceptor : Interceptor {
// limit requests sent to NewMotion to 3 per second
rateLimiter.acquire()
var response: Response = chain.proceed(request)
var response: Response = chain.proceedSafely(request)
// 403 is how the NewMotion API indicates a rate limit error
if (!response.isSuccessful && response.code == 403) {
response.close()
@@ -25,11 +42,11 @@ class RateLimitInterceptor : Interceptor {
Thread.sleep(1000)
} catch (e: InterruptedException) {
}
response = chain.proceed(request)
response = chain.proceedSafely(request)
}
return response
} else {
return chain.proceed(request)
return chain.proceedSafely(request)
}
}
}
@@ -20,7 +20,8 @@ private val plugNames = mapOf(
Chargepoint.SUPERCHARGER to R.string.plug_supercharger,
Chargepoint.CEE_BLAU to R.string.plug_cee_blau,
Chargepoint.CEE_ROT to R.string.plug_cee_rot,
Chargepoint.TESLA_ROADSTER_HPC to R.string.plug_roadster_hpc
Chargepoint.TESLA_ROADSTER_HPC to R.string.plug_roadster_hpc,
Chargepoint.MCS to R.string.plug_mcs
)
fun nameForPlugType(ctx: StringProvider, type: String): String =
@@ -63,6 +64,7 @@ fun iconForPlugType(type: String): Int =
Chargepoint.CEE_BLAU -> R.drawable.ic_connector_cee_blau
Chargepoint.CEE_ROT -> R.drawable.ic_connector_cee_rot
Chargepoint.TYPE_1 -> R.drawable.ic_connector_typ1
Chargepoint.MCS -> R.drawable.ic_connector_mcs
// TODO: add other connectors
else -> R.drawable.ic_connector_unknown
}
@@ -4,6 +4,7 @@ import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import net.vonforst.evmap.api.proceedSafely
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.utils.distanceBetween
@@ -86,15 +87,15 @@ interface EnBwApi {
.addInterceptor { chain ->
// add API key to every request
val request = chain.request().newBuilder()
.header("Ocp-Apim-Subscription-Key", "d4954e8b2e444fc89a89a463788c0a72")
.header("Ocp-Apim-Subscription-Key", "90a67b9900364009b588e100e4b1cc64")
.header("Origin", "https://www.enbw.com")
.header("Referer", "https://www.enbw.com/")
.header("Accept", "application/json")
.build()
chain.proceed(request)
chain.proceedSafely(request)
}.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl ?: "https://enbw-emp.azure-api.net/emobility-public-api/api/v1/")
.baseUrl(baseUrl ?: "https://api.emp.emob-enbw.com/emobility-public-api/api/v1/")
.addConverterFactory(
MoshiConverterFactory.create(
Moshi.Builder().add(InstantAdapter()).build()
@@ -7,6 +7,7 @@ import net.vonforst.evmap.api.availability.tesla.asTeslaCoord
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Chargepoint
import okhttp3.OkHttpClient
import retrofit2.HttpException
import java.time.Instant
import java.util.Collections
@@ -229,12 +230,21 @@ class TeslaOwnerAvailabilityDetector(
?: run {
val refreshToken = tokenStore.teslaRefreshToken
?: throw NotSignedInException()
val response =
val response = try {
authApi.getToken(
TeslaAuthenticationApi.RefreshTokenRequest(
refreshToken
)
)
} catch (e: HttpException) {
if (e.code() == 401) {
// refresh token is invalid, clear it
tokenStore.teslaRefreshToken = null
throw NotSignedInException()
} else {
throw e
}
}
tokenStore.teslaAccessToken = response.accessToken
tokenStore.teslaAccessTokenExpiry = now + response.expiresIn
response.accessToken
@@ -3,6 +3,7 @@ package net.vonforst.evmap.api.availability.tesla
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import net.vonforst.evmap.api.proceedSafely
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import retrofit2.Retrofit
@@ -48,7 +49,7 @@ interface TeslaCuaApi {
val request = chain.request().newBuilder()
.cacheControl(CacheControl.Builder().maxStale(24, TimeUnit.HOURS).build())
.build()
chain.proceed(request)
chain.proceedSafely(request)
}.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl ?: "https://www.tesla.com/cua-api/")
@@ -7,6 +7,7 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import kotlinx.coroutines.runBlocking
import net.vonforst.evmap.api.proceedSafely
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
@@ -25,7 +26,7 @@ interface TeslaAuthenticationApi {
class AuthCodeRequest(
val code: String,
@Json(name = "code_verifier") val codeVerifier: String,
@Json(name = "redirect_uri") val redirectUri: String = "https://auth.tesla.com/void/callback",
@Json(name = "redirect_uri") val redirectUri: String = "tesla://auth/callback",
scope: String = "openid email offline_access",
@Json(name = "client_id") clientId: String = "ownerapi"
) : OAuth2Request(scope, clientId)
@@ -98,13 +99,13 @@ interface TeslaAuthenticationApi {
.appendQueryParameter("client_id", "ownerapi")
.appendQueryParameter("code_challenge", codeChallenge)
.appendQueryParameter("code_challenge_method", "S256")
.appendQueryParameter("redirect_uri", "https://auth.tesla.com/void/callback")
.appendQueryParameter("redirect_uri", "tesla://auth/callback")
.appendQueryParameter("response_type", "code")
.appendQueryParameter("scope", "openid email offline_access phone")
.appendQueryParameter("is_in_app", "true")
.appendQueryParameter("state", "123").build()
val resultUrlPrefix = "https://auth.tesla.com/void/callback"
val resultUrlPrefix = "tesla://auth/callback"
}
}
@@ -135,7 +136,7 @@ interface TeslaOwnerApi {
.header("x-tesla-user-agent", "TeslaApp/4.44.5-3304/3a5d531cc3/android/27")
.header("Accept", "*/*")
.build()
chain.proceed(request)
chain.proceedSafely(request)
}.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl ?: "https://owner-api.teslamotors.com")
@@ -376,7 +377,7 @@ interface TeslaChargingOwnershipGraphQlApi {
.header("x-tesla-user-agent", "TeslaApp/4.44.5-3304/3a5d531cc3/android/27")
.header("Accept", "*/*")
.build()
chain.proceed(request)
chain.proceedSafely(request)
}.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl ?: "https://akamai-apigateway-charging-ownership.tesla.com")
@@ -4,6 +4,7 @@ import android.content.Context
import com.squareup.moshi.Moshi
import net.vonforst.evmap.BuildConfig
import net.vonforst.evmap.addDebugInterceptors
import net.vonforst.evmap.api.proceedSafely
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Chargepoint
import okhttp3.Cache
@@ -46,7 +47,7 @@ private interface FronyxApiRetrofit {
.header("X-API-Token", apikey)
.header("Content-Type", "application/json")
.build()
chain.proceed(new)
chain.proceedSafely(new)
}
if (BuildConfig.DEBUG) {
addDebugInterceptors()
@@ -1,7 +1,14 @@
package net.vonforst.evmap.api.goingelectric
import android.util.Log
import com.squareup.moshi.*
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonQualifier
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.Types
import java.lang.reflect.Type
import java.time.Instant
import java.time.LocalTime
@@ -67,58 +74,95 @@ internal class ChargepointListItemJsonAdapter(val moshi: Moshi) :
}
}
internal class JsonObjectOrFalseAdapter<T> private constructor(
private val objectDelegate: JsonAdapter<T>,
private val clazz: Class<*>
) : JsonAdapter<T>() {
class Factory : JsonAdapter.Factory {
override fun create(
type: Type,
annotations: Set<Annotation>,
moshi: Moshi
): JsonAdapter<Any>? {
val clazz = Types.getRawType(type)
return when (hasJsonObjectOrFalseAnnotation(
annotations
)) {
false -> null
true -> JsonObjectOrFalseAdapter(
moshi.adapter(type), clazz
)
}
}
}
@Suppress("UNCHECKED_CAST")
override fun fromJson(reader: JsonReader): T? = when (reader.peek()) {
class StringOrFalseAdapter {
@FromJson
@JsonObjectOrFalse
fun fromJson(reader: JsonReader): String? = when (reader.peek()) {
JsonReader.Token.BOOLEAN -> when (reader.nextBoolean()) {
false -> null // Response was false
else -> {
if (this.clazz == GEFaultReport::class.java) {
GEFaultReport(null, "") as T
} else {
throw IllegalStateException("Non-false boolean for @JsonObjectOrFalse field")
}
}
false -> null
true -> throw IllegalArgumentException("found true")
}
JsonReader.Token.BEGIN_OBJECT -> objectDelegate.fromJson(reader)
JsonReader.Token.BEGIN_ARRAY -> objectDelegate.fromJson(reader)
JsonReader.Token.STRING -> objectDelegate.fromJson(reader)
JsonReader.Token.NUMBER -> objectDelegate.fromJson(reader)
else ->
throw IllegalStateException("Non-object-non-boolean value for @JsonObjectOrFalse field")
JsonReader.Token.STRING -> reader.nextString()
else -> throw IllegalArgumentException("illegal value found")
}
override fun toJson(writer: JsonWriter, value: T?) = objectDelegate.toJson(writer, value)
@ToJson
fun toJson(writer: JsonWriter, @JsonObjectOrFalse value: String?) {
writer.value(value)
}
}
private fun hasJsonObjectOrFalseAnnotation(annotations: Set<Annotation>?) =
annotations?.firstOrNull { it.annotationClass == JsonObjectOrFalse::class } != null
class IntOrFalseAdapter {
@FromJson
@JsonObjectOrFalse
fun fromJson(reader: JsonReader): Int? = when (reader.peek()) {
JsonReader.Token.BOOLEAN -> when (reader.nextBoolean()) {
false -> null
true -> throw IllegalArgumentException("found true")
}
JsonReader.Token.NUMBER -> reader.nextInt()
else -> throw IllegalArgumentException("illegal value found")
}
@ToJson
fun toJson(writer: JsonWriter, @JsonObjectOrFalse value: Int?) {
writer.value(value)
}
}
class GEFaultReportOrFalseAdapter {
@FromJson
@JsonObjectOrFalse
fun fromJson(reader: JsonReader, delegate: JsonAdapter<GEFaultReport>): GEFaultReport? =
when (reader.peek()) {
JsonReader.Token.BOOLEAN -> when (reader.nextBoolean()) {
false -> null
true -> GEFaultReport(null, "")
}
else -> delegate.fromJson(reader)
}
@ToJson
fun toJson(
writer: JsonWriter,
@JsonObjectOrFalse value: GEFaultReport?,
delegate: JsonAdapter<GEFaultReport>
) {
delegate.toJson(writer, value)
}
}
class ChargeCardListOrFalseAdapter {
@FromJson
@JsonObjectOrFalse
fun fromJson(
reader: JsonReader,
delegate: JsonAdapter<List<GEChargeCardId>>
): List<GEChargeCardId>? = when (reader.peek()) {
JsonReader.Token.BOOLEAN -> when (reader.nextBoolean()) {
false -> null
true -> throw IllegalArgumentException("found true")
}
else -> delegate.fromJson(reader)
}
@ToJson
fun toJson(
writer: JsonWriter,
@JsonObjectOrFalse value: List<GEChargeCardId>?,
delegate: JsonAdapter<List<GEChargeCardId>>
) {
delegate.toJson(writer, value)
}
}
@JsonQualifier
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class JsonObjectOrFalse {
}
@@ -21,6 +21,7 @@ import net.vonforst.evmap.api.mapPower
import net.vonforst.evmap.api.mapPowerInverse
import net.vonforst.evmap.api.nameForPlugType
import net.vonforst.evmap.api.powerSteps
import net.vonforst.evmap.api.proceedSafely
import net.vonforst.evmap.model.BooleanFilter
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Chargepoint
@@ -112,7 +113,10 @@ interface GoingElectricApi {
private val moshi = Moshi.Builder()
.add(ChargepointListItemJsonAdapterFactory())
.add(JsonObjectOrFalseAdapter.Factory())
.add(StringOrFalseAdapter())
.add(IntOrFalseAdapter())
.add(GEFaultReportOrFalseAdapter())
.add(ChargeCardListOrFalseAdapter())
.add(HoursAdapter())
.add(InstantAdapter())
.build()
@@ -128,7 +132,7 @@ interface GoingElectricApi {
var original = chain.request()
val url = original.url.newBuilder().addQueryParameter("key", apikey).build()
original = original.newBuilder().url(url).build()
chain.proceed(original)
chain.proceedSafely(original)
}
if (BuildConfig.DEBUG) {
addDebugInterceptors()
@@ -234,7 +234,8 @@ class NobilApiWrapper(
Chargepoint.TYPE_2_PLUG,
Chargepoint.CCS_UNKNOWN,
Chargepoint.CHADEMO,
Chargepoint.SUPERCHARGER
Chargepoint.SUPERCHARGER,
Chargepoint.MCS
)
val connectorsMap = connectors.associateWith { connector ->
nameForPlugType(sp, connector)
@@ -232,7 +232,7 @@ data class NobilChargerStation(
"40" -> Chargepoint.SUPERCHARGER // Tesla Connector Model
"70" -> return null // Hydrogen
"82" -> return null // Biogas
"87" -> "" // MCS
"87" -> Chargepoint.MCS
// These are deprecated and not used
"50" -> "" // Type 2 + Schuko
@@ -16,6 +16,7 @@ import net.vonforst.evmap.api.StringProvider
import net.vonforst.evmap.api.mapPower
import net.vonforst.evmap.api.mapPowerInverse
import net.vonforst.evmap.api.powerSteps
import net.vonforst.evmap.api.proceedSafely
import net.vonforst.evmap.model.BooleanFilter
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.ChargepointListItem
@@ -101,7 +102,7 @@ interface OpenChargeMapApi {
val new = original.newBuilder()
.header("X-API-Key", apikey)
.build()
chain.proceed(new)
chain.proceedSafely(new)
}
if (BuildConfig.DEBUG) {
addDebugInterceptors()
@@ -1,19 +1,23 @@
package net.vonforst.evmap.fragment
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.View
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import net.vonforst.evmap.R
import net.vonforst.evmap.adapter.ConnectorAdapter
import net.vonforst.evmap.adapter.ConnectorDetailsAdapter
import net.vonforst.evmap.adapter.SingleViewAdapter
import net.vonforst.evmap.api.availability.ChargeLocationStatus
import net.vonforst.evmap.api.iconForPlugType
import net.vonforst.evmap.api.nameForPlugType
import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.databinding.DialogConnectorDetailsBinding
import net.vonforst.evmap.databinding.DialogConnectorDetailsHeaderBinding
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.ui.availabilityColor
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.goneUnless
import java.util.Locale
class ConnectorDetailsDialog(
binding: DialogConnectorDetailsBinding,
@@ -30,9 +34,10 @@ class ConnectorDetailsDialog(
layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
headerBinding_ = DataBindingUtil.inflate(
headerBinding_ = DialogConnectorDetailsHeaderBinding.inflate(
LayoutInflater.from(context),
R.layout.dialog_connector_details_header, binding.list, false
binding.list,
false
)
binding.list.adapter = ConcatAdapter(
SingleViewAdapter(headerBinding.root),
@@ -57,8 +62,41 @@ class ConnectorDetailsDialog(
} else emptyList()
detailsAdapter.submitList(items)
headerBinding.divider.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
headerBinding.item = ConnectorAdapter.ChargepointWithAvailability(cp, cpStatus)
headerBinding.divider.visibility = goneUnless(items.isNotEmpty())
val context = headerBinding.root.context
val locale = Locale.getDefault()
val provider = context.stringProvider()
headerBinding.imageView.setImageResource(iconForPlugType(cp.type))
headerBinding.imageView.contentDescription = cp.type
headerBinding.imageView.imageTintList =
ColorStateList.valueOf(availabilityColor(cpStatus, context))
headerBinding.txtNumber.text = String.format(locale, "x %d", cp.count)
headerBinding.txtNumber.visibility = goneUnless(cpStatus == null)
if (cpStatus != null) {
headerBinding.txtStatus.text = String.format(
locale,
"%s/%d",
availabilityText(cpStatus),
cp.count
)
headerBinding.txtStatus.backgroundTintList =
ColorStateList.valueOf(availabilityColor(cpStatus, context))
}
headerBinding.txtStatus.visibility = goneUnless(cpStatus != null)
val name = nameForPlugType(provider, cp.type)
headerBinding.txtTitle.text = if (cp.hasKnownPower()) {
"$name - ${cp.formatPower(locale)}"
} else {
name
}
headerBinding.txtDetail.text = cp.formatVoltageAndCurrent()
headerBinding.txtDetail.visibility = goneUnless(cp.hasKnownVoltageAndCurrent())
}
fun onDestroy() {
@@ -10,7 +10,6 @@ import android.widget.FrameLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
@@ -32,6 +31,7 @@ import net.vonforst.evmap.databinding.ItemFavoriteBinding
import net.vonforst.evmap.location.FusionEngine
import net.vonforst.evmap.location.LocationEngine
import net.vonforst.evmap.model.FavoriteWithDetail
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.utils.checkAnyLocationPermission
import net.vonforst.evmap.viewmodel.FavoritesViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
@@ -64,12 +64,7 @@ class FavoritesFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_favorites, container, false
)
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = vm
binding = FragmentFavoritesBinding.inflate(inflater, container, false)
ViewCompat.setOnApplyWindowInsetsListener(
binding.favsList
@@ -114,6 +109,13 @@ class FavoritesFragment : Fragment() {
)
)
}
vm.listData.observe(viewLifecycleOwner) { items ->
adapter.submitList(items)
val isEmpty = items.isNullOrEmpty()
binding.animationView.visibility = goneUnless(isEmpty)
binding.txtEmptyState.visibility = goneUnless(isEmpty)
}
createTouchHelper().attachToRecyclerView(binding.favsList)
binding.swipeRefresh.setOnRefreshListener {
@@ -185,7 +187,7 @@ class FavoritesFragment : Fragment() {
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (viewHolder != null && actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFavoriteBinding
getDefaultUIUtil().onSelected(binding.foreground)
} else {
super.onSelectedChanged(viewHolder, actionState)
@@ -199,7 +201,7 @@ class FavoritesFragment : Fragment() {
) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFavoriteBinding
getDefaultUIUtil().onDrawOver(
c, recyclerView, binding.foreground, dX, dY,
actionState, isCurrentlyActive
@@ -229,7 +231,7 @@ class FavoritesFragment : Fragment() {
viewHolder: RecyclerView.ViewHolder
) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFavoriteBinding
getDefaultUIUtil().clearView(binding.foreground)
}
@@ -240,7 +242,7 @@ class FavoritesFragment : Fragment() {
) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFavoriteBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFavoriteBinding
getDefaultUIUtil().onDraw(
c, recyclerView, binding.foreground, dX, dY,
actionState, isCurrentlyActive
@@ -12,7 +12,6 @@ import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
@@ -34,6 +33,7 @@ import net.vonforst.evmap.viewmodel.FilterViewModel
class FilterFragment : Fragment(), MenuProvider {
private lateinit var binding: FragmentFilterBinding
private lateinit var filtersAdapter: FiltersAdapter
private val vm: FilterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -47,10 +47,7 @@ class FilterFragment : Fragment(), MenuProvider {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_filter, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = vm
vm.filterProfile.observe(viewLifecycleOwner) {}
binding = FragmentFilterBinding.inflate(inflater, container, false)
ViewCompat.setOnApplyWindowInsetsListener(
binding.filtersList
@@ -77,8 +74,9 @@ class FilterFragment : Fragment(), MenuProvider {
}
}
filtersAdapter = FiltersAdapter()
binding.filtersList.apply {
adapter = FiltersAdapter()
adapter = filtersAdapter
layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
addItemDecoration(
@@ -88,6 +86,13 @@ class FilterFragment : Fragment(), MenuProvider {
)
}
vm.filtersWithValue.observe(viewLifecycleOwner) {
@Suppress("UNCHECKED_CAST")
val items =
it as? List<net.vonforst.evmap.model.FilterWithValue<net.vonforst.evmap.model.FilterValue>>
filtersAdapter.submitList(items)
}
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
@@ -31,6 +31,7 @@ import net.vonforst.evmap.adapter.FilterProfilesAdapter
import net.vonforst.evmap.databinding.FragmentFilterProfilesBinding
import net.vonforst.evmap.databinding.ItemFilterProfileBinding
import net.vonforst.evmap.storage.FilterProfile
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.ui.showEditTextDialog
import net.vonforst.evmap.viewmodel.FilterProfilesViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
@@ -60,8 +61,6 @@ class FilterProfilesFragment : Fragment() {
savedInstanceState: Bundle?
): View {
binding = FragmentFilterProfilesBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = vm
ViewCompat.setOnApplyWindowInsetsListener(
binding.filterProfilesList
@@ -116,7 +115,7 @@ class FilterProfilesFragment : Fragment() {
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (viewHolder != null && actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFilterProfileBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFilterProfileBinding
getDefaultUIUtil().onSelected(binding.foreground)
} else {
super.onSelectedChanged(viewHolder, actionState)
@@ -130,7 +129,7 @@ class FilterProfilesFragment : Fragment() {
) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFilterProfileBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFilterProfileBinding
getDefaultUIUtil().onDrawOver(
c, recyclerView, binding.foreground, dX, dY,
actionState, isCurrentlyActive
@@ -160,7 +159,7 @@ class FilterProfilesFragment : Fragment() {
viewHolder: RecyclerView.ViewHolder
) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFilterProfileBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFilterProfileBinding
getDefaultUIUtil().clearView(binding.foreground)
}
@@ -171,7 +170,7 @@ class FilterProfilesFragment : Fragment() {
) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
val binding =
(viewHolder as DataBindingAdapter.ViewHolder<*>).binding as ItemFilterProfileBinding
(viewHolder as DataBindingAdapter.ViewHolder).binding as ItemFilterProfileBinding
getDefaultUIUtil().onDraw(
c, recyclerView, binding.foreground, dX, dY,
actionState, isCurrentlyActive
@@ -223,6 +222,11 @@ class FilterProfilesFragment : Fragment() {
)
}
vm.filterProfiles.observe(viewLifecycleOwner) { profiles ->
adapter.submitList(profiles)
binding.txtEmptyState.visibility = goneUnless(profiles.isNullOrEmpty())
}
touchHelper.attachToRecyclerView(binding.filterProfilesList)
binding.toolbar.setNavigationOnClickListener {
@@ -8,11 +8,13 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.format.DateUtils
import android.text.method.KeyListener
import android.view.ContextThemeWrapper
import android.view.Gravity
@@ -31,18 +33,19 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresPermission
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat
import androidx.core.content.ContextCompat
import androidx.core.location.LocationListenerCompat
import androidx.core.view.MenuCompat
import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.core.view.ViewGroupCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnLayout
import androidx.core.view.doOnNextLayout
import androidx.core.view.updateLayoutParams
import androidx.databinding.DataBindingUtil
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
@@ -86,7 +89,10 @@ import net.vonforst.evmap.adapter.ConnectorAdapter
import net.vonforst.evmap.adapter.DetailsAdapter
import net.vonforst.evmap.adapter.GalleryAdapter
import net.vonforst.evmap.adapter.PlaceAutocompleteAdapter
import net.vonforst.evmap.adapter.buildDetails
import net.vonforst.evmap.adapter.chargepointWithAvailability
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.autocomplete.ApiUnavailableException
import net.vonforst.evmap.autocomplete.PlaceWithBounds
import net.vonforst.evmap.bold
@@ -105,13 +111,20 @@ import net.vonforst.evmap.shouldUseImperialUnits
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.HideOnScrollFabBehavior
import net.vonforst.evmap.ui.MarkerManager
import net.vonforst.evmap.ui.availabilityColor
import net.vonforst.evmap.ui.availabilityText
import net.vonforst.evmap.ui.distance
import net.vonforst.evmap.ui.flatten
import net.vonforst.evmap.ui.goneUnless
import net.vonforst.evmap.ui.invisibleUnless
import net.vonforst.evmap.ui.isFabActive
import net.vonforst.evmap.ui.setTouchModal
import net.vonforst.evmap.utils.boundingBox
import net.vonforst.evmap.utils.checkAnyLocationPermission
import net.vonforst.evmap.utils.checkFineLocationPermission
import net.vonforst.evmap.utils.distanceBetween
import net.vonforst.evmap.utils.formatDMS
import net.vonforst.evmap.utils.formatDecimal
import net.vonforst.evmap.viewmodel.GalleryViewModel
import net.vonforst.evmap.viewmodel.MapPosition
import net.vonforst.evmap.viewmodel.MapViewModel
import net.vonforst.evmap.viewmodel.Resource
@@ -119,6 +132,7 @@ import net.vonforst.evmap.viewmodel.Status
import java.io.IOException
import java.time.Duration
import java.time.Instant
import java.util.Locale
import kotlin.math.min
@@ -126,7 +140,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
private var _binding: FragmentMapBinding? = null
private val binding get() = _binding!!
private val vm: MapViewModel by viewModels()
private val galleryVm: GalleryViewModel by activityViewModels()
private var mapFragment: MapFragment? = null
private var map: AnyMap? = null
private var markerManager: MarkerManager? = null
@@ -191,10 +204,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DataBindingUtil.inflate(inflater, R.layout.fragment_map, container, false)
println(binding.detailView.sourceButton)
binding.lifecycleOwner = viewLifecycleOwner
binding.vm = vm
_binding = FragmentMapBinding.inflate(inflater, container, false)
val provider = prefs.mapProvider
if (mapFragment == null) {
@@ -221,6 +231,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
com.google.android.material.R.style.Theme_Material3_DayNight
val density = resources.displayMetrics.density
ViewGroupCompat.installCompatInsetsDispatch(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.detailAppBar.toolbar) { v, insets ->
val systemWindowInsetTop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@@ -321,6 +334,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
vm.apiName.observe(viewLifecycleOwner) {
binding.detailAppBar.toolbar.menu.findItem(R.id.menu_edit).title =
getString(R.string.edit_at_datasource, it)
renderDetailView()
}
binding.detailView.topPart.doOnNextLayout {
@@ -333,6 +347,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
setupObservers()
setupClickListeners()
setupAdapters()
renderDetailView()
(activity as? MapsActivity)?.setSupportActionBar(binding.toolbar)
binding.toolbar.setupWithNavController(
@@ -436,6 +451,16 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
binding.fabLayers.setOnClickListener {
openLayersMenu()
}
binding.layers.radioGroup.setOnCheckedChangeListener { group, i ->
when (i) {
R.id.rbStandard -> vm.setMapType(AnyMap.Type.NORMAL)
R.id.rbSatellite -> vm.setMapType(AnyMap.Type.HYBRID)
R.id.rbTerrain -> vm.setMapType(AnyMap.Type.TERRAIN)
}
}
binding.layers.cbTraffic.setOnCheckedChangeListener { _, isChecked ->
vm.mapTrafficEnabled.value = isChecked
}
binding.layers.btnClose.setOnClickListener {
closeLayersMenu()
}
@@ -567,6 +592,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
@SuppressLint("SetTextI18n")
private fun setupSearchAutocomplete() {
binding.search.threshold = 1
binding.search.doAfterTextChanged {
updateClearSearchVisibility()
}
searchKeyListener = binding.search.keyListener
binding.search.keyListener = null
@@ -594,6 +622,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
place.primaryText.toString()
}
)
updateClearSearchVisibility()
}
binding.search.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
@@ -607,11 +636,186 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
binding.clearSearch.setOnClickListener {
vm.searchResult.value = null
removeSearchFocus()
updateClearSearchVisibility()
}
binding.toolbar.doOnLayout {
binding.search.dropDownWidth = binding.toolbar.width
binding.search.dropDownAnchor = R.id.toolbar
}
updateClearSearchVisibility()
}
private fun updateClearSearchVisibility() {
binding.clearSearch.visibility = invisibleUnless(!binding.search.text.isNullOrEmpty())
}
private fun updateChargepointsUi(res: Resource<List<ChargepointListItem>>) {
binding.progressBar2.visibility = goneUnless(res.status == Status.LOADING)
binding.progressBar2.isIndeterminate = res.progress == null
binding.progressBar2.progress = ((res.progress ?: 0f) * 100f).toInt().coerceIn(0, 100)
binding.search.hint = if (res.progress != null) {
getString(R.string.downloading_chargers_percent, (res.progress * 100f).coerceIn(0f, 100f))
} else {
getString(R.string.search)
}
}
private fun updateGallery(photos: List<ChargerPhoto>?) {
val galleryAdapter = binding.gallery.adapter as? GalleryAdapter ?: return
galleryAdapter.submitList(photos)
binding.galleryPlaceholder.visibility = invisibleUnless(photos.isNullOrEmpty())
}
private fun renderDetailView() {
if (_binding == null) return
val context = requireContext()
val charger = vm.charger.value?.data
val availability = vm.availability.value
val filteredAvailability = vm.filteredAvailability.value
val predictionData = vm.predictionData.value
val chargeCards = vm.chargeCardMap.value
val filteredChargeCards = vm.filteredChargeCards.value
val distanceMeters = vm.chargerDistance.value
val expanded = vm.bottomSheetExpanded.value == true
val teslaPricing = vm.teslaPricing.value
val apiName = vm.apiName.value.orEmpty()
binding.detailView.apply {
txtName.text = charger?.name
txtName.maxLines = if (expanded) 3 else 1
txtAddress.text = charger?.address?.toString() ?: charger?.coordinates?.formatDMS()
txtAddress.visibility = invisibleUnless(charger != null)
txtDistance.text = distance(distanceMeters, context)
txtConnectors.text =
charger?.formatChargepoints(context.stringProvider(), Locale.getDefault())
val hasConnectors = charger?.chargepointsMerged?.isNotEmpty() == true
connectors.visibility = goneUnless(hasConnectors)
txtConnectorsHeading.visibility = goneUnless(hasConnectors)
txtRealtimeDesc.visibility = goneUnless(hasConnectors)
btnRefreshLiveData.visibility = goneUnless(hasConnectors)
btnRefreshLiveData.isEnabled = availability?.status != Status.LOADING
txtRealtimeDesc.text = when {
availability?.status == Status.SUCCESS -> getString(
R.string.realtime_data_source,
availability.data?.source.orEmpty()
)
availability?.status == Status.LOADING -> getString(R.string.realtime_data_loading)
availability?.message == "not signed in" -> getString(R.string.realtime_data_login_needed)
else -> getString(R.string.realtime_data_unavailable)
}
btnLogin.visibility = goneUnless(
hasConnectors && availability?.status == Status.ERROR && availability.message == "not signed in"
)
val flattenedAvailability = flatten(filteredAvailability?.data?.status?.values)
if (flattenedAvailability != null && !expanded) {
txtAvailability.visibility = View.VISIBLE
txtAvailability.text = String.format(
Locale.getDefault(),
"%s/%d",
availabilityText(flattenedAvailability),
filteredAvailability?.data?.totalChargepoints ?: 0
)
txtAvailability.backgroundTintList = ColorStateList.valueOf(
availabilityColor(flattenedAvailability, context)
)
} else {
txtAvailability.visibility = View.INVISIBLE
}
(connectors.adapter as? ConnectorAdapter)?.submitList(
chargepointWithAvailability(
charger?.chargepointsMerged,
availability?.data?.status
)
)
(details.adapter as? DetailsAdapter)?.submitList(
buildDetails(charger, chargeCards, filteredChargeCards, teslaPricing, context)
)
val amenities = charger?.amenities
val hasAmenities = !amenities.isNullOrBlank()
txtAmenitiesHeading.visibility = goneUnless(hasAmenities)
txtAmenities.visibility = goneUnless(hasAmenities)
txtAmenities.text = amenities
val generalInformation = charger?.generalInformation
val hasGeneralInformation = !generalInformation.isNullOrBlank()
txtGeneralInformationHeading.visibility = goneUnless(hasGeneralInformation)
txtGeneralInformation.visibility = goneUnless(hasGeneralInformation)
txtGeneralInformation.text = generalInformation
sourceButton.text = getString(R.string.source, apiName)
val hasChargeprice = charger?.let { ChargepriceApi.isChargerSupported(it) } == true
val hasChargerWebsite = charger?.chargerUrl != null
val hasExternalLinks = charger != null && (hasChargeprice || hasChargerWebsite)
buttonsScroller.visibility = goneUnless(hasExternalLinks)
divider3.visibility = goneUnless(hasExternalLinks)
btnChargeprice.visibility = goneUnless(hasChargeprice)
btnChargerWebsite.visibility = goneUnless(hasChargerWebsite)
val hasPredictionGraph = predictionData?.predictionGraph != null
val isPredictionPercentage = predictionData?.isPercentage == true
val predictionDescription = predictionData?.description
val showPredictionInfo = hasPredictionGraph && !isPredictionPercentage
txtPredictionHeading.visibility = goneUnless(hasPredictionGraph)
txtPredictionHeading.text = if (isPredictionPercentage) {
getString(R.string.average_utilization)
} else {
getString(R.string.utilization_prediction)
}
prediction.visibility = goneUnless(hasPredictionGraph)
prediction.data = predictionData?.predictionGraph
prediction.maxValue = predictionData?.maxValue
prediction.isPercentage = isPredictionPercentage
txtPredictionDescription.text = predictionDescription
txtPredictionDescription.visibility =
goneUnless(showPredictionInfo && !predictionDescription.isNullOrBlank())
btnPredictionHelp.visibility = goneUnless(showPredictionInfo)
imgPredictionSource.visibility = goneUnless(showPredictionInfo)
divider1.visibility = goneUnless(hasPredictionGraph)
imgVerified.visibility = goneUnless(charger?.verified == true)
TooltipCompat.setTooltipText(imgVerified, getString(R.string.verified_desc, apiName))
imgFaultReport.visibility = goneUnless(charger?.faultReport != null)
TooltipCompat.setTooltipText(imgFaultReport, getString(R.string.fault_report))
val timeRetrieved = charger?.timeRetrieved
val showTimeRetrieved = timeRetrieved != null &&
Duration.between(timeRetrieved, Instant.now()) > Duration.ofHours(1)
txtTimeRetrieved.visibility = goneUnless(showTimeRetrieved)
val shownTimeRetrieved = if (showTimeRetrieved) timeRetrieved else null
if (shownTimeRetrieved != null) {
txtTimeRetrieved.text = getString(
R.string.data_retrieved_at,
DateUtils.getRelativeTimeSpanString(
shownTimeRetrieved.toEpochMilli(),
Instant.now().toEpochMilli(),
0
)
)
} else {
txtTimeRetrieved.text = null
}
val license = charger?.license
txtLicense.visibility = goneUnless(!license.isNullOrBlank())
txtLicense.text = license
}
}
private fun removeSearchFocus() {
@@ -684,7 +888,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
mapBottomPadding + min(bottomSheetBehavior.peekHeight, height)
)
}
println(slideOffset)
if (bottomSheetBehavior.state != STATE_HIDDEN) {
binding.navBarScrim.visibility = View.VISIBLE
binding.navBarScrim.translationY =
@@ -735,6 +938,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
}
}
vm.chargepoints.observe(viewLifecycleOwner, Observer { res ->
updateChargepointsUi(res)
val chargepoints = res.data
if (chargepoints != null) {
markerManager?.chargepoints = chargepoints
@@ -772,21 +976,72 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
vm.searchResult.observe(viewLifecycleOwner) { place ->
displaySearchResult(place, moveCamera = true)
}
vm.charger.observe(viewLifecycleOwner) {
updateGallery(it?.data?.photos)
renderDetailView()
}
vm.availability.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.filteredAvailability.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.predictionData.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.chargeCardMap.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.filteredChargeCards.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.chargerDistance.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.bottomSheetExpanded.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.teslaPricing.observe(viewLifecycleOwner) {
renderDetailView()
}
vm.myLocationEnabled.observe(viewLifecycleOwner) {
isFabActive(binding.fabLocate, it == true)
}
vm.bottomSheetState.observe(viewLifecycleOwner) {
binding.navBarScrim.visibility = invisibleUnless(it == STATE_COLLAPSED)
}
vm.layersMenuOpen.observe(viewLifecycleOwner) { open ->
HideOnScrollFabBehavior.from(binding.fabLayers)?.hidden = open
binding.fabLayers.visibility = if (open) View.INVISIBLE else View.VISIBLE
binding.layersSheet.visibility = if (open) View.VISIBLE else View.INVISIBLE
binding.fabLayers.visibility = invisibleUnless(!open)
binding.layersSheet.visibility = invisibleUnless(open)
updateBackPressedCallback()
}
vm.mapType.observe(viewLifecycleOwner) {
map?.setMapType(it)
when (it) {
AnyMap.Type.NORMAL -> binding.layers.rbStandard.isChecked = true
AnyMap.Type.HYBRID -> binding.layers.rbSatellite.isChecked = true
AnyMap.Type.TERRAIN -> binding.layers.rbTerrain.isChecked = true
else -> {
binding.layers.rbStandard.isChecked = false
binding.layers.rbSatellite.isChecked = false
binding.layers.rbTerrain.isChecked = false
}
}
}
vm.mapTrafficEnabled.observe(viewLifecycleOwner) {
map?.setTrafficEnabled(it)
if (binding.layers.cbTraffic.isChecked != it) {
binding.layers.cbTraffic.isChecked = it
}
}
vm.mapTrafficSupported.observe(viewLifecycleOwner) { supported ->
val visibility = goneUnless(supported)
binding.layers.textView23.visibility = visibility
binding.layers.cbTraffic.visibility = visibility
}
vm.selectedChargepoint.observe(viewLifecycleOwner) {
binding.detailView.connectorDetailsCard.visibility =
if (it != null) View.VISIBLE else View.INVISIBLE
binding.detailView.connectorDetailsCard.visibility = invisibleUnless(it != null)
if (it != null) {
detailsDialog.setData(it, vm.availability.value?.data)
}
@@ -1267,7 +1522,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
if (filterBadge != null) {
// set up badge showing number of active filters
vm.filtersCount.observe(viewLifecycleOwner) {
filterBadge.visibility = if (it > 0) View.VISIBLE else View.GONE
filterBadge.visibility = goneUnless(it > 0)
filterBadge.text = it.toString()
}
}
@@ -1469,4 +1724,4 @@ class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
(split-screen mode) and then have references to a destroyed fragment. */
popupMenu?.dismiss()
}
}
}
@@ -4,14 +4,20 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CompoundButton
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewbinding.ViewBinding
import net.vonforst.evmap.R
import net.vonforst.evmap.adapter.DataBindingAdapter
import net.vonforst.evmap.adapter.Equatable
import net.vonforst.evmap.databinding.DialogMultiSelectBinding
import net.vonforst.evmap.databinding.DialogMultiSelectItemBinding
import net.vonforst.evmap.ui.MaterialDialogFragment
import java.util.*
import java.util.HashMap
import java.util.HashSet
import java.util.Locale
class MultiSelectDialog : MaterialDialogFragment() {
companion object {
@@ -121,8 +127,26 @@ private fun search(
}
}
class Adapter() : DataBindingAdapter<MultiSelectItem>({ it.key }) {
class Adapter : DataBindingAdapter<MultiSelectItem>({ it.key }) {
override fun getItemViewType(position: Int) = R.layout.dialog_multi_select_item
override fun createBinding(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int
): ViewBinding = DialogMultiSelectItemBinding.inflate(inflater, parent, false)
override fun bind(holder: ViewHolder, item: MultiSelectItem) {
super.bind(holder, item)
val binding = holder.binding as DialogMultiSelectItemBinding
val checkBox = binding.root.findViewById<CheckBox>(android.R.id.text1)
checkBox.text = item.name
checkBox.setOnCheckedChangeListener(null)
checkBox.isChecked = item.selected
checkBox.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
item.selected = isChecked
}
}
}
data class MultiSelectItem(val key: String, val name: CharSequence, var selected: Boolean) :
@@ -6,17 +6,14 @@ import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Bundle
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.style.URLSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import android.widget.ScrollView
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.core.text.getSpans
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
@@ -31,7 +28,6 @@ import net.vonforst.evmap.databinding.FragmentOnboardingWelcomeBinding
import net.vonforst.evmap.model.FILTERS_DISABLED
import net.vonforst.evmap.navigation.safeNavigate
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.CustomUrlSpan
import net.vonforst.evmap.ui.replaceUrlSpansWithCustom
import net.vonforst.evmap.waitForLayout
@@ -267,6 +263,9 @@ class DataSourceSelectFragment : OnboardingPageFragment() {
ObjectAnimator.ofFloat(binding.btnGetStarted, "alpha", 0f, 1f).apply {
interpolator = DecelerateInterpolator()
}.start()
(binding.root as? ScrollView)?.let {
it.smoothScrollBy(0, it.height)
}
}
}
}
@@ -1,15 +1,18 @@
package net.vonforst.evmap.fragment.preference
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.core.net.toUri
import androidx.preference.CheckBoxPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.github.erfansn.localeconfigx.configuredLocales
import com.google.android.material.snackbar.Snackbar
import net.vonforst.evmap.R
import net.vonforst.evmap.isAppInstalled
import net.vonforst.evmap.ui.getAppLocale
@@ -83,9 +86,23 @@ class UiSettingsFragment : BaseSettingsFragment() {
val context = context ?: return false
val intent = Intent(
Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:${context.packageName}")
"package:${context.packageName}".toUri()
)
context.startActivity(intent)
try {
context.startActivity(intent)
} catch (_: ActivityNotFoundException) {
view?.let { v ->
Snackbar.make(
v,
R.string.system_setting_not_supported,
Snackbar.LENGTH_SHORT
).show()
}
Log.e(
"UiSettingsFragment",
"Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS activity not found"
)
}
}
return true
}
@@ -470,6 +470,7 @@ data class Chargepoint(
const val CEE_BLAU = "CEE Blau"
const val CEE_ROT = "CEE Rot"
const val TESLA_ROADSTER_HPC = "Tesla HPC"
const val MCS = "Megawatt Charging System"
}
}
@@ -1,6 +1,5 @@
package net.vonforst.evmap.model
import androidx.databinding.BaseObservable
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
@@ -46,7 +45,7 @@ data class SliderFilter(
override fun defaultValue() = SliderFilterValue(key, min)
}
sealed class FilterValue : BaseObservable(), Equatable {
sealed class FilterValue : Equatable {
abstract val key: String
var dataSource: String = ""
var profile: Long = FILTERS_CUSTOM
@@ -94,7 +94,10 @@ class CacheLiveData<T>(
// Both cache and API have finished loading
value = when (api.status) {
Status.SUCCESS -> api
Status.ERROR -> Resource.error(api.message, cache.data)
Status.ERROR -> {
Log.e("CacheLiveData", "API returned an error: ${api.message}")
Resource.error(api.message, cache.data)
}
Status.LOADING -> api // should not occur
}
}
@@ -40,7 +40,7 @@ import net.vonforst.evmap.model.SliderFilterValue
OCMOperator::class,
OSMNetwork::class,
SavedRegion::class
], version = 28
], version = 29
)
@TypeConverters(Converters::class, GeometryConverters::class)
abstract class AppDatabase : RoomDatabase() {
@@ -85,7 +85,7 @@ abstract class AppDatabase : RoomDatabase() {
MIGRATION_12, MIGRATION_13, MIGRATION_14, MIGRATION_15, MIGRATION_16,
MIGRATION_17, MIGRATION_18, MIGRATION_19, MIGRATION_20, MIGRATION_21,
MIGRATION_22, MIGRATION_23, MIGRATION_24, MIGRATION_25, MIGRATION_26,
MIGRATION_27, MIGRATION_28
MIGRATION_27, MIGRATION_28, MIGRATION_29
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
@@ -555,6 +555,14 @@ abstract class AppDatabase : RoomDatabase() {
db.execSQL("DELETE FROM ChargeLocation WHERE `dataSource` = 'nobil'")
}
}
private val MIGRATION_29 = object : Migration(28, 29) {
override fun migrate(db: SupportSQLiteDatabase) {
// Force nobil data refresh to update MCS connectors
db.execSQL("DELETE FROM SavedRegion WHERE `dataSource` = 'nobil'")
db.execSQL("DELETE FROM ChargeLocation WHERE `dataSource` = 'nobil'")
}
}
}
/**
@@ -2,188 +2,40 @@ package net.vonforst.evmap.ui
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.text.format.DateUtils
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.widget.TooltipCompat
import androidx.core.content.ContextCompat
import androidx.core.content.res.use
import androidx.core.text.HtmlCompat
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
import androidx.databinding.InverseBindingListener
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import coil.load
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.slider.RangeSlider
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
import net.vonforst.evmap.api.iconForPlugType
import net.vonforst.evmap.isDarkMode
import net.vonforst.evmap.kmPerMile
import net.vonforst.evmap.meterPerFt
import net.vonforst.evmap.shouldUseImperialUnits
import java.time.Instant
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.roundToInt
fun goneUnless(visible: Boolean): Int = if (visible) View.VISIBLE else View.GONE
@BindingAdapter("goneUnless")
fun goneUnless(view: View, visible: Boolean) {
view.visibility = if (visible) View.VISIBLE else View.GONE
}
fun invisibleUnless(visible: Boolean): Int = if (visible) View.VISIBLE else View.INVISIBLE
@BindingAdapter("goneUnlessAnimated")
fun goneUnlessAnimated(view: View, oldValue: Boolean, newValue: Boolean) {
if (oldValue == newValue) return
view.animate().cancel()
if (newValue) {
view.visibility = View.VISIBLE
view.alpha = 0f
view.animate().alpha(1f).withEndAction {
view.alpha = 1f
}
} else {
view.animate().alpha(0f).withEndAction {
view.alpha = 1f
view.visibility = View.GONE
}
}
}
@BindingAdapter("invisibleUnless")
fun invisibleUnless(view: View, visible: Boolean) {
view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
@BindingAdapter("invisibleUnlessAnimated")
fun invisibleUnlessAnimated(view: View, oldValue: Boolean, newValue: Boolean) {
if (oldValue == newValue) {
if (!newValue && view.visibility == View.VISIBLE && view.alpha == 1f) {
// view is initially invisible
view.visibility = View.INVISIBLE
} else {
return
}
}
view.animate().cancel()
if (newValue) {
view.visibility = View.VISIBLE
view.alpha = 0f
view.animate().alpha(1f).withEndAction {
view.alpha = 1f
}
} else {
view.animate().alpha(0f).withEndAction {
view.alpha = 1f
view.visibility = View.INVISIBLE
}
}
}
@BindingAdapter("isFabActive")
fun isFabActive(view: FloatingActionButton, isColored: Boolean) {
view.imageTintList = activeTint(view.context, isColored)
}
@BindingAdapter("backgroundTintActive")
fun backgroundTintActive(view: View, isColored: Boolean) {
view.backgroundTintList = activeTint(view.context, isColored)
}
@BindingAdapter("imageTintActive")
fun imageTintActive(view: ImageView, isColored: Boolean) {
view.imageTintList = activeTint(view.context, isColored)
}
private fun activeTint(
context: Context,
isColored: Boolean
): ColorStateList {
val color = context.theme.obtainStyledAttributes(
intArrayOf(
if (isColored) {
androidx.appcompat.R.attr.colorPrimary
} else {
androidx.appcompat.R.attr.colorControlNormal
}
)
)
val valueOf = ColorStateList.valueOf(color.getColor(0, 0))
return valueOf
}
@BindingAdapter("data")
@Suppress("UNCHECKED_CAST")
fun <T> setRecyclerViewData(recyclerView: RecyclerView, items: List<T>?) {
if (recyclerView.adapter is ListAdapter<*, *>) {
(recyclerView.adapter as ListAdapter<T, *>).submitList(items)
private fun activeTint(context: Context, isColored: Boolean): ColorStateList {
val colorAttr = if (isColored) {
androidx.appcompat.R.attr.colorPrimary
} else {
androidx.appcompat.R.attr.colorControlNormal
}
return context.obtainStyledAttributes(intArrayOf(colorAttr)).use {
ColorStateList.valueOf(it.getColor(0, 0))
}
}
@BindingAdapter("data")
@Suppress("UNCHECKED_CAST")
fun <T> setRecyclerViewData(recyclerView: ViewPager2, items: List<T>?) {
if (recyclerView.adapter is ListAdapter<*, *>) {
(recyclerView.adapter as ListAdapter<T, *>).submitList(items)
}
}
@BindingAdapter("connectorIcon")
fun getConnectorItem(view: ImageView, type: String?) {
view.setImageResource(type?.let { iconForPlugType(it) } ?: 0)
}
@BindingAdapter("srcCompat")
fun setImageResource(imageView: ImageView, resource: Int) {
imageView.setImageResource(resource)
}
@BindingAdapter("android:contentDescription")
fun setContentDescriptionResource(imageView: ImageView, resource: Int) {
imageView.contentDescription = imageView.context.getString(resource)
}
@BindingAdapter("tintAvailability")
fun setImageTintAvailability(view: ImageView, available: List<ChargepointStatus>?) {
view.imageTintList = ColorStateList.valueOf(availabilityColor(available, view.context))
}
@BindingAdapter("tintAvailability")
fun setImageTintAvailability(view: ImageView, available: ChargepointStatus?) {
view.imageTintList = ColorStateList.valueOf(availabilityColor(available, view.context))
}
@BindingAdapter("textColorAvailability")
fun setTextColorAvailability(view: TextView, available: List<ChargepointStatus>?) {
view.setTextColor(availabilityColor(available, view.context))
}
@BindingAdapter("textColorAvailability")
fun setTextColorAvailability(view: TextView, available: ChargepointStatus?) {
view.setTextColor(availabilityColor(available, view.context))
}
@BindingAdapter("backgroundTintAvailability")
fun setBackgroundTintAvailability(view: View, available: List<ChargepointStatus>?) {
view.backgroundTintList = ColorStateList.valueOf(availabilityColor(available, view.context))
}
@BindingAdapter("selectableItemBackground")
fun applySelectableItemBackground(view: View, apply: Boolean) {
if (apply) {
view.context.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.selectableItemBackground))
@@ -195,92 +47,36 @@ fun applySelectableItemBackground(view: View, apply: Boolean) {
}
}
@BindingAdapter("htmlText")
fun setHtmlTextValue(textView: TextView, htmlText: String?) {
if (htmlText == null) {
textView.text = null
} else {
textView.text = HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
}
@BindingAdapter("android:layout_marginTop")
fun setTopMargin(view: View, topMargin: Float) {
val layoutParams = view.layoutParams as MarginLayoutParams
layoutParams.setMargins(
layoutParams.leftMargin, topMargin.roundToInt(),
layoutParams.rightMargin, layoutParams.bottomMargin
)
view.layoutParams = layoutParams
}
/**
* Linkify is already possible using the autoLink and linksClickable attributes, but this does not
* remove spans correctly after autoLink is set to false.
* So we implement a new version that manually uses Linkify to create links if necessary.
*/
@BindingAdapter(value = ["linkify", "android:text"])
fun setLinkify(
textView: TextView,
oldLinkify: Int,
oldText: CharSequence?,
newLinkify: Int,
newText: CharSequence?
) {
if (oldLinkify == newLinkify && oldText == newText) return
textView.text = newText
if (newLinkify != 0) {
Linkify.addLinks(textView, newLinkify)
fun setLinkify(textView: TextView, linkifyMask: Int, text: CharSequence?) {
textView.text = text
if (linkifyMask != 0) {
Linkify.addLinks(textView, linkifyMask)
textView.movementMethod = LinkMovementMethod.getInstance()
} else {
textView.movementMethod = null
}
}
@BindingAdapter("chargepriceTagColor")
fun setChargepriceTagColor(view: TextView, kind: String) {
view.backgroundTintList = ColorStateList.valueOf(
ContextCompat.getColor(
view.context,
when (kind) {
"star" -> R.color.chargeprice_star
"alert" -> R.color.chargeprice_alert
"info" -> R.color.chargeprice_info
"lock" -> R.color.chargeprice_lock
else -> R.color.chip_background
}
)
)
}
private fun availabilityColor(
status: List<ChargepointStatus>?,
context: Context
): Int = if (status != null) {
fun availabilityColor(status: List<ChargepointStatus>?, context: Context): Int =
if (status != null) {
val unknown = status.any { it == ChargepointStatus.UNKNOWN }
val available = status.count { it == ChargepointStatus.AVAILABLE }
val allFaulted = status.all { it == ChargepointStatus.FAULTED }
if (unknown) {
ContextCompat.getColor(context, R.color.unknown)
} else if (available > 0) {
ContextCompat.getColor(context, R.color.available)
} else if (allFaulted) {
ContextCompat.getColor(context, R.color.unavailable)
} else {
ContextCompat.getColor(context, R.color.charging)
when {
unknown -> ContextCompat.getColor(context, R.color.unknown)
available > 0 -> ContextCompat.getColor(context, R.color.available)
allFaulted -> ContextCompat.getColor(context, R.color.unavailable)
else -> ContextCompat.getColor(context, R.color.charging)
}
} else {
val ta =
context.theme.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.colorControlNormal))
ta.getColor(0, 0)
context.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.colorControlNormal))
.use {
it.getColor(0, 0)
}
}
private fun availabilityColor(
status: ChargepointStatus?,
context: Context
): Int = when (status) {
fun availabilityColor(status: ChargepointStatus?, context: Context): Int = when (status) {
ChargepointStatus.UNKNOWN -> ContextCompat.getColor(context, R.color.unknown)
ChargepointStatus.AVAILABLE -> ContextCompat.getColor(context, R.color.available)
ChargepointStatus.FAULTED -> ContextCompat.getColor(context, R.color.unavailable)
@@ -289,28 +85,26 @@ private fun availabilityColor(
R.color.charging
)
null -> {
val ta =
context.theme.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.colorControlNormal))
ta.getColor(0, 0)
null -> context.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.colorControlNormal))
.use {
it.getColor(0, 0)
}
}
fun availabilityText(status: List<ChargepointStatus>?): String? {
if (status == null) return null
val total = status.size
val unknown = status.count { it == ChargepointStatus.UNKNOWN }
val available = status.count { it == ChargepointStatus.AVAILABLE }
return if (unknown > 0) {
if (unknown == total) "?" else "$available?"
} else available.toString()
} else {
available.toString()
}
}
fun availabilityText(status: ChargepointStatus?, lastChange: Instant?, context: Context): String? {
if (status == null) return null
val statusText = when (status) {
ChargepointStatus.UNKNOWN -> context.getString(R.string.status_unknown)
ChargepointStatus.AVAILABLE -> context.getString(R.string.status_available)
@@ -318,60 +112,49 @@ fun availabilityText(status: ChargepointStatus?, lastChange: Instant?, context:
ChargepointStatus.OCCUPIED -> context.getString(R.string.status_occupied)
ChargepointStatus.FAULTED -> context.getString(R.string.status_faulted)
}
return if (lastChange != null) {
val relativeTime = DateUtils.getRelativeTimeSpanString(
lastChange.toEpochMilli(),
Instant.now().toEpochMilli(),
0
).toString()
return context.getString(R.string.status_since, statusText, relativeTime)
context.getString(R.string.status_since, statusText, relativeTime)
} else {
statusText
}
}
fun flatten(it: Iterable<Iterable<ChargepointStatus>>?): List<ChargepointStatus>? {
return it?.flatten()
fun flatten(values: Iterable<Iterable<ChargepointStatus>>?): List<ChargepointStatus>? {
return values?.flatten()
}
fun currency(currency: String): String {
// shorthands for currencies
return when (currency) {
"EUR" -> ""
"USD" -> "$"
"DKK", "SEK", "NOK" -> "kr."
"PLN" -> ""
"CHF" -> "Fr. "
"CZK" -> ""
"GBP" -> "£"
"HRK" -> "kn"
"HUF" -> "Ft"
"ISK" -> "kr"
else -> currency
}
}
fun time(value: Int): String {
val h = floor(value.toDouble() / 60).toInt()
val min = ceil(value.toDouble() % 60).toInt()
return if (h == 0 && min > 0) "$min min"
else "%d:%02d h".format(h, min)
fun currency(currency: String): String = when (currency) {
"EUR" -> ""
"USD" -> "$"
"DKK", "SEK", "NOK" -> "kr."
"PLN" -> ""
"CHF" -> "Fr. "
"CZK" -> ""
"GBP" -> "£"
"HRK" -> "kn"
"HUF" -> "Ft"
"ISK" -> "kr"
else -> currency
}
fun distance(meters: Number?, ctx: Context): String? {
if (meters == null) return null
if (shouldUseImperialUnits(ctx)) {
return if (shouldUseImperialUnits(ctx)) {
val ft = meters.toDouble() / meterPerFt
val mi = meters.toDouble() / 1e3 / kmPerMile
return when {
when {
ft < 1000 -> "%.0f ft".format(ft)
mi < 10 -> "%.1f mi".format(mi)
else -> "%.0f mi".format(mi)
}
} else {
val km = meters.toDouble() / 1e3
return when {
when {
km < 1 -> "%.0f m".format(meters.toDouble())
km < 10 -> "%.1f km".format(km)
else -> "%.0f km".format(km)
@@ -379,103 +162,3 @@ fun distance(meters: Number?, ctx: Context): String? {
}
}
@InverseBindingAdapter(attribute = "values")
fun getRangeSliderValue(slider: RangeSlider) = slider.values
@BindingAdapter("valuesAttrChanged")
fun setRangeSliderListeners(slider: RangeSlider, attrChange: InverseBindingListener) {
slider.addOnChangeListener { _, _, _ ->
attrChange.onChange()
}
}
@ColorInt
fun colorEnabled(ctx: Context, enabled: Boolean): Int {
val attr = if (enabled) {
android.R.attr.textColorSecondary
} else {
android.R.attr.textColorHint
}
val typedValue = ctx.obtainStyledAttributes(intArrayOf(attr))
val color = typedValue.getColor(0, 0)
typedValue.recycle()
return color
}
@BindingAdapter("tint")
fun setImageTintList(view: ImageView, @ColorInt color: Int) {
view.imageTintList = ColorStateList.valueOf(color)
}
fun tariffBackground(context: Context, myTariff: Boolean, brandingColor: String?): Drawable? {
when {
myTariff -> {
return ContextCompat.getDrawable(context, R.drawable.my_tariff_background)
}
brandingColor != null -> {
val drawable = ContextCompat.getDrawable(context, R.drawable.branded_tariff_background)
val color = colorToTransparent(Color.parseColor(brandingColor))
(drawable as LayerDrawable).setDrawableByLayerId(
R.id.background, ColorDrawable(
color
)
)
return drawable
}
else -> {
context.obtainStyledAttributes(intArrayOf(androidx.appcompat.R.attr.selectableItemBackground))
.use {
return it.getDrawable(0)
}
}
}
}
fun isDarkMode(context: Context) = context.isDarkMode()
/**
* Converts an opaque color to a transparent color, assuming it was on a white background
* with a certain opacity targetAlpha.
*/
private fun colorToTransparent(color: Int, targetAlpha: Float = 31f / 255): Int {
if (Color.alpha(color) != 255) return color
val red = Color.red(color)
val green = Color.green(color)
val blue = Color.blue(color)
val newRed = kotlin.math.max(((red - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
val newGreen =
kotlin.math.max(((green - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
val newBlue = kotlin.math.max(((blue - (1 - targetAlpha) * 255) / targetAlpha).roundToInt(), 0)
return Color.argb((targetAlpha * 255).roundToInt(), newRed, newGreen, newBlue)
}
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
if (url != null) {
view.load(url)
} else {
view.setImageDrawable(null)
}
}
@BindingAdapter("tooltipTextCompat")
fun setTooltipTextCompat(view: View, text: String) {
TooltipCompat.setTooltipText(view, text)
}
@BindingAdapter("tintNullable")
fun setImageTint(view: ImageView, @ColorInt tint: Int?) {
if (tint != null) {
view.imageTintList = ColorStateList.valueOf(tint)
} else {
view.imageTintList = null
}
}
@BindingAdapter("isPercentage")
fun setIsPercentage(view: BarGraphView, value: Boolean) {
view.isPercentage = value
}
@@ -0,0 +1,51 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7.57,2.904A5.168,5.168 0,0 0,2.4 8.074a5.168,5.168 0,0 0,1.097 3.181l5.656,7.24A3.616,3.616 0,0 0,12 19.885a3.616,3.616 0,0 0,2.843 -1.389l5.656,-7.24A5.168,5.168 0,0 0,21.6 8.074a5.168,5.168 0,0 0,-5.17 -5.17z"
android:strokeWidth="2"
android:fillColor="#000000"
android:fillAlpha="0"
android:strokeColor="#000"/>
<path
android:fillColor="#FF000000"
android:pathData="M11.5,10.159a1,1 0,0 0,-1 1,1 1,0 0,0 1,1h1a1,1 0,0 0,1 -1,1 1,0 0,0 -1,-1z"/>
<path
android:pathData="M7.5,8.074m-2.9,0a2.9,2.9 0,1 1,5.8 0a2.9,2.9 0,1 1,-5.8 0"
android:strokeWidth="1"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M16.5,8.074m-2.9,0a2.9,2.9 0,1 1,5.8 0a2.9,2.9 0,1 1,-5.8 0"
android:strokeWidth="1"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:fillColor="#FF000000"
android:pathData="M9,13m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M15,13m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"/>
<path
android:pathData="M12,15.5m-1.8,0a1.8,1.8 0,1 1,3.6 0a1.8,1.8 0,1 1,-3.6 0"
android:strokeWidth="1"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
</vector>
@@ -12,6 +12,7 @@
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:requiresFadingEdge="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/welcomeTitle"
app:layout_constraintHorizontal_bias="0.5"
@@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fillViewport="true">
android:fillViewport="true"
android:requiresFadingEdge="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -30,13 +31,16 @@
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="32dp"
android:gravity="center"
android:text="@string/pref_data_source"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
app:layout_constraintBottom_toTopOf="@+id/welcomeText2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintVertical_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/welcomeText2"
@@ -61,7 +65,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/data_sources_hint"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
+59 -161
View File
@@ -1,91 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
<com.google.android.material.card.MaterialCardView xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="java.util.Map" />
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
<import type="java.time.ZonedDateTime" />
<import type="net.vonforst.evmap.model.ChargeLocation" />
<import type="net.vonforst.evmap.model.Chargepoint" />
<import type="net.vonforst.evmap.model.ChargeCard" />
<import type="net.vonforst.evmap.api.availability.ChargeLocationStatus" />
<import type="net.vonforst.evmap.adapter.DataBindingAdaptersKt" />
<import type="net.vonforst.evmap.adapter.DetailsAdapterKt" />
<import type="net.vonforst.evmap.utils.LocationUtilsKt" />
<import type="net.vonforst.evmap.viewmodel.Resource" />
<import type="net.vonforst.evmap.viewmodel.Status" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="net.vonforst.evmap.api.ChargepointApiKt" />
<import type="net.vonforst.evmap.api.chargeprice.ChargepriceApi" />
<import type="android.text.format.DateUtils" />
<import type="java.time.Instant" />
<import type="java.time.Duration" />
<import type="net.vonforst.evmap.api.fronyx.PredictionData" />
<variable
name="charger"
type="Resource&lt;ChargeLocation&gt;" />
<variable
name="distance"
type="Double" />
<variable
name="availability"
type="Resource&lt;ChargeLocationStatus&gt;" />
<variable
name="predictionData"
type="PredictionData" />
<variable
name="filteredAvailability"
type="Resource&lt;ChargeLocationStatus&gt;" />
<variable
name="teslaPricing"
type="net.vonforst.evmap.api.availability.tesla.Pricing" />
<variable
name="chargeCards"
type="java.util.Map&lt;Long, ChargeCard&gt;" />
<variable
name="filteredChargeCards"
type="java.util.Set&lt;Long&gt;" />
<variable
name="expanded"
type="Boolean" />
<variable
name="apiName"
type="String" />
</data>
<com.google.android.material.card.MaterialCardView
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -107,8 +23,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:hyphenationFrequency="normal"
android:maxLines="@{expanded ? 3 : 1}"
android:text="@{charger.data.name}"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/imgFaultReport"
@@ -119,15 +34,14 @@
tools:text="Parkhaus" />
<TextView
android:id="@+id/textView2"
android:id="@+id/txtAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAlignment="viewStart"
android:text="@{charger.data != null ? (charger.data.address != null ? charger.data.address.toString() : LocationUtilsKt.formatDMS(charger.data.coordinates)) : null }"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:invisibleUnless="@{charger.data != null}"
android:visibility="invisible"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/txtName"
@@ -142,7 +56,6 @@
android:textAlignment="viewEnd"
android:maxLines="1"
android:minWidth="50dp"
android:text="@{BindingAdaptersKt.distance(distance, context)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="@+id/txtConnectors"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
@@ -158,11 +71,9 @@
android:gravity="end"
android:maxLines="1"
android:padding="2dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;%s/%d&quot;, BindingAdaptersKt.availabilityText(BindingAdaptersKt.flatten(filteredAvailability.data.status.values())), filteredAvailability.data.totalChargepoints)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@android:color/white"
app:backgroundTintAvailability="@{BindingAdaptersKt.flatten(filteredAvailability.data.status.values())}"
app:invisibleUnless="@{filteredAvailability.data != null &amp;&amp; !expanded}"
android:visibility="invisible"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/txtName"
tools:backgroundTint="@color/available"
@@ -176,29 +87,27 @@
android:ellipsize="end"
android:maxLines="1"
android:textAlignment="viewStart"
android:text="@{charger.data.formatChargepoints(ChargepointApiKt.stringProvider(context), LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintEnd_toStartOf="@+id/txtDistance"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/txtAddress"
tools:text="2x Typ 2 22 kW" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/connectors"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:data="@{DataBindingAdaptersKt.chargepointWithAvailability(charger.data.chargepointsMerged, availability.data.status)}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView7"
app:goneUnless="@{charger.data.chargepointsMerged.size() > 0}"
app:layout_constraintTop_toBottomOf="@+id/txtConnectorsHeading"
tools:itemCount="3"
tools:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_connector"
tools:orientation="horizontal" />
<TextView
android:id="@+id/textView7"
android:id="@+id/txtConnectorsHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -207,69 +116,67 @@
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?colorPrimary"
android:textAlignment="viewStart"
app:goneUnless="@{charger.data.chargepointsMerged.size() > 0}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/btnRefreshLiveData"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/txtConnectors" />
<TextView
android:id="@+id/textView12"
android:id="@+id/txtAmenitiesHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/amenities"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?colorPrimary"
app:goneUnless="@{charger.data.amenities != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/details" />
<TextView
android:id="@+id/textView11"
android:id="@+id/txtAmenities"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:autoLink="web"
android:linksClickable="true"
android:text="@{charger.data.amenities}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:goneUnless="@{charger.data.amenities != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView12"
app:layout_constraintTop_toBottomOf="@+id/txtAmenitiesHeading"
tools:text="Toilet" />
<TextView
android:id="@+id/textView10"
android:id="@+id/txtGeneralInformationHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/general_info"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?colorPrimary"
app:goneUnless="@{charger.data.generalInformation != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView11" />
app:layout_constraintTop_toBottomOf="@+id/txtAmenities" />
<TextView
android:id="@+id/textView4"
android:id="@+id/txtGeneralInformation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:autoLink="web"
android:linksClickable="true"
android:text="@{charger.data.generalInformation}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:goneUnless="@{charger.data.generalInformation != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/textView10"
app:layout_constraintTop_toBottomOf="@+id/txtGeneralInformationHeading"
tools:text="Only for guests" />
<androidx.constraintlayout.widget.Guideline
@@ -291,7 +198,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:data="@{DetailsAdapterKt.buildDetails(charger.data, chargeCards, filteredChargeCards, teslaPricing, context)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
@@ -313,21 +219,21 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@{@string/source(apiName)}"
android:text=""
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView4"
app:layout_constraintTop_toBottomOf="@+id/txtGeneralInformation"
tools:text="Source: DataSource" />
<TextView
android:id="@+id/textView13"
android:id="@+id/txtRealtimeDesc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:gravity="end"
android:text="@{availability.status == Status.SUCCESS ? @string/realtime_data_source(availability.data.source) : availability.status == Status.LOADING ? @string/realtime_data_loading : availability.message == &quot;not signed in&quot; ? @string/realtime_data_login_needed : @string/realtime_data_unavailable}"
android:text="@string/realtime_data_unavailable"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{charger.data.chargepointsMerged.size() > 0}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/btnLogin"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/connectors"
@@ -350,7 +256,7 @@
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@+id/textView13" />
app:layout_constraintTop_toBottomOf="@+id/txtRealtimeDesc" />
<View
android:id="@+id/divider3"
@@ -358,34 +264,33 @@
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider"
app:goneUnless="@{charger.data != null &amp;&amp; (ChargepriceApi.isChargerSupported(charger.data) || charger.data.chargerUrl != null)}"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/buttonsScroller" />
<TextView
android:id="@+id/textView8"
android:id="@+id/txtPredictionHeading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@{predictionData.isPercentage ? @string/average_utilization : @string/utilization_prediction}"
android:text="@string/utilization_prediction"
tools:text="@string/utilization_prediction"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textColor="?colorPrimary"
app:goneUnless="@{predictionData.predictionGraph != null}"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/divider2" />
<TextView
android:id="@+id/textView29"
android:id="@+id/txtPredictionDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="@{predictionData.description}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{predictionData.predictionGraph != null &amp;&amp; !predictionData.isPercentage}"
app:layout_constraintBaseline_toBaselineOf="@+id/textView8"
android:visibility="gone"
app:layout_constraintBaseline_toBaselineOf="@+id/txtPredictionHeading"
app:layout_constraintEnd_toStartOf="@+id/btnPredictionHelp"
app:layout_constraintStart_toEndOf="@+id/textView8"
app:layout_constraintStart_toEndOf="@+id/txtPredictionHeading"
tools:text="(DC plugs only)" />
<Button
@@ -394,25 +299,22 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/help"
app:goneUnless="@{predictionData.predictionGraph != null &amp;&amp; !predictionData.isPercentage}"
android:visibility="gone"
app:icon="@drawable/ic_help"
app:iconTint="?android:textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/textView8"
app:layout_constraintBottom_toBottomOf="@+id/txtPredictionHeading"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView8" />
app:layout_constraintTop_toTopOf="@+id/txtPredictionHeading" />
<net.vonforst.evmap.ui.BarGraphView
android:id="@+id/prediction"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
app:data="@{predictionData.predictionGraph}"
app:goneUnless="@{predictionData.predictionGraph != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/textView8"
app:maxValue="@{predictionData.maxValue}"
app:isPercentage="@{predictionData.isPercentage}"
app:layout_constraintTop_toBottomOf="@+id/txtPredictionHeading"
tools:itemCount="3"
tools:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_connector"
@@ -426,7 +328,7 @@
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless"
android:scaleType="fitCenter"
app:goneUnless="@{predictionData.predictionGraph != null &amp;&amp; !predictionData.isPercentage}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/prediction"
app:srcCompat="@drawable/ic_powered_by_fronyx"
@@ -438,7 +340,7 @@
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider"
app:goneUnless="@{predictionData.predictionGraph != null}"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/imgPredictionSource" />
<ImageView
@@ -449,14 +351,13 @@
android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:contentDescription="@string/verified"
app:goneUnless="@{ charger.data.verified }"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/txtAvailability"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/imgFaultReport"
app:layout_constraintTop_toTopOf="@+id/txtName"
app:srcCompat="@drawable/ic_verified"
app:tint="@color/available"
app:tooltipTextCompat="@{@string/verified_desc(apiName)}"
tools:targetApi="o" />
<ImageView
@@ -466,12 +367,11 @@
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:contentDescription="@string/fault_report"
app:goneUnless="@{ charger.data.faultReport != null }"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/imgVerified"
app:layout_constraintStart_toEndOf="@+id/txtName"
app:layout_constraintTop_toTopOf="@+id/txtName"
app:srcCompat="@drawable/ic_map_marker_fault"
app:tooltipTextCompat="@{@string/fault_report}"
tools:targetApi="o" />
<TextView
@@ -480,11 +380,11 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:breakStrategy="balanced"
android:text="@{@string/data_retrieved_at(DateUtils.getRelativeTimeSpanString(charger.data.timeRetrieved.toEpochMilli(), Instant.now().toEpochMilli(), 0))}"
android:text=""
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textStyle="italic"
app:goneUnless="@{charger.data.timeRetrieved == null || Duration.between(charger.data.timeRetrieved, Instant.now()).compareTo(Duration.ofHours(1)) > 0}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/sourceButton"
@@ -496,11 +396,11 @@
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:breakStrategy="balanced"
android:text="@{charger.data.license}"
android:text=""
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textStyle="italic"
app:goneUnless="@{charger.data.license != null}"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/txtTimeRetrieved"
@@ -512,13 +412,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/refresh_live_data"
android:enabled="@{availability.status != Status.LOADING}"
app:goneUnless="@{charger.data.chargepointsMerged.size() > 0}"
android:visibility="gone"
app:icon="@drawable/ic_refresh"
app:iconTint="?android:textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/textView7"
app:layout_constraintBottom_toBottomOf="@+id/txtConnectorsHeading"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView7" />
app:layout_constraintTop_toTopOf="@+id/txtConnectorsHeading" />
<HorizontalScrollView
android:id="@+id/buttonsScroller"
@@ -530,7 +429,7 @@
app:layout_constraintTop_toBottomOf="@+id/divider1"
app:layout_constrainedWidth="true"
android:fillViewport="true"
app:goneUnless="@{charger.data != null &amp;&amp; (ChargepriceApi.isChargerSupported(charger.data) || charger.data.chargerUrl != null)}">
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
@@ -543,7 +442,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/go_to_chargeprice"
app:goneUnless="@{charger.data != null &amp;&amp; ChargepriceApi.isChargerSupported(charger.data)}" />
android:visibility="gone" />
<Button
android:id="@+id/btnChargerWebsite"
@@ -552,7 +451,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/charger_website"
app:goneUnless="@{charger.data != null &amp;&amp; charger.data.chargerUrl != null}"
android:visibility="gone"
app:icon="@drawable/ic_link" />
</LinearLayout>
@@ -564,10 +463,10 @@
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="@string/login"
app:goneUnless="@{availability.status == Status.ERROR &amp;&amp; availability.message == &quot;not signed in&quot;}"
app:layout_constraintBottom_toBottomOf="@+id/textView13"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/txtRealtimeDesc"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/textView13" />
app:layout_constraintTop_toTopOf="@+id/txtRealtimeDesc" />
<com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewElevatedStyle"
android:id="@+id/connector_details_card"
@@ -589,5 +488,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
</com.google.android.material.card.MaterialCardView>
@@ -1,116 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding">
<data>
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:tintMode="src_in"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/divider"
tools:srcCompat="@drawable/ic_connector_typ2"
tools:tint="@color/available" />
<import type="net.vonforst.evmap.adapter.ConnectorAdapter.ChargepointWithAvailability" />
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="net.vonforst.evmap.api.UtilsKt" />
<import type="net.vonforst.evmap.api.ChargepointApiKt" />
<variable
name="item"
type="ChargepointWithAvailability" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
<TextView
android:id="@+id/txtNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding">
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:text="x99"
tools:visibility="gone" />
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:contentDescription="@{item.chargepoint.type}"
android:tintMode="src_in"
app:connectorIcon="@{item.chargepoint.type}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/divider"
app:tintAvailability="@{item.status}"
tools:srcCompat="@drawable/ic_connector_typ2"
tools:tint="@color/available" />
<TextView
android:id="@+id/txtStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;\u00D7 %d&quot;, item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.status == null}"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:text="×99"
tools:visibility="gone" />
<TextView
android:id="@+id/txtTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="4dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toTopOf="@id/txtDetail"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView"
tools:text="CCS - 350 kW" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;%s/%d&quot;, BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@android:color/white"
app:backgroundTintAvailability="@{item.status}"
app:goneUnless="@{item.status != null}"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<TextView
android:id="@+id/txtDetail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTitle"
app:layout_constraintTop_toBottomOf="@id/txtTitle"
tools:text="1000 V - 500 A" />
<TextView
android:id="@+id/textView6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="4dp"
android:text="@{item != null ? UtilsKt.nameForPlugType(ChargepointApiKt.stringProvider(context), item.chargepoint.type) + (item.chargepoint.hasKnownPower() ? &quot; · &quot; + item.chargepoint.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context)) : &quot;&quot;) : null}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toTopOf="@id/textView8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@id/imageView"
tools:text="CCS · 350 kW" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="4dp"
android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/textView8"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="@{item.chargepoint.formatVoltageAndCurrent()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.chargepoint.hasKnownVoltageAndCurrent()}"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/textView6"
app:layout_constraintTop_toBottomOf="@id/textView6"
tools:text="1000 V · 500 A" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="4dp"
android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,70 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding">
<data>
<ImageView
android:id="@+id/imageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="center"
android:tintMode="src_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/circle"
tools:tint="@color/available" />
<import type="net.vonforst.evmap.adapter.ConnectorDetailsAdapter.ConnectorDetails" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="java.time.Instant" />
<variable
name="item"
type="ConnectorDetails" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
<TextView
android:id="@+id/txtEvseid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding">
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="SK*IOY*E222901" />
<ImageView
android:id="@+id/imageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@{BindingAdaptersKt.availabilityText(item.status, (Instant) null, context)}"
android:scaleType="center"
android:tintMode="src_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tintAvailability="@{item.status}"
app:srcCompat="@drawable/circle"
tools:tint="@color/available" />
<TextView
android:id="@+id/txtStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtEvseid"
app:layout_constraintTop_toBottomOf="@+id/txtEvseid"
tools:text="Available" />
<TextView
android:id="@+id/txtEvseid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:text="@{(item.label != null &amp;&amp; item.evseId != null) ? item.label + &quot; · &quot; + item.evseId : (item.label ?? item.evseId)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="SK*IOY*E222901" />
<TextView
android:id="@+id/txtStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@{BindingAdaptersKt.availabilityText(item.status, item.lastChange, context)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.status != null}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtEvseid"
app:layout_constraintTop_toBottomOf="@+id/txtEvseid"
tools:text="Available" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,27 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="net.vonforst.evmap.fragment.MultiSelectItem" />
<variable
name="item"
type="MultiSelectItem" />
</data>
<CheckBox
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding"
android:paddingStart="20dp"
android:ellipsize="marquee"
android:text="@{item.name}"
android:checked="@={item.selected}"
tools:text="Item"
tools:ignore="RtlSymmetry" />
</layout>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding"
android:paddingStart="20dp"
android:ellipsize="marquee"
tools:text="Item"
tools:ignore="RtlSymmetry" />
+38 -54
View File
@@ -1,20 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="net.vonforst.evmap.viewmodel.FavoritesViewModel" />
<variable
name="vm"
type="FavoritesViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_container"
@@ -46,45 +35,40 @@
android:id="@+id/favs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:data="@{vm.listData}" />
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
app:goneUnless="@{vm.listData.size() == 0}"
app:layout_constraintBottom_toTopOf="@+id/textView19"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
app:layout_constraintVertical_chainStyle="packed"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/ev_anim" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/txtEmptyState"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
app:layout_constraintVertical_chainStyle="packed"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/ev_anim" />
<TextView
android:id="@+id/textView19"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:breakStrategy="balanced"
android:gravity="center_horizontal"
android:text="@string/favorites_empty_state"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?android:textColorSecondary"
app:goneUnless="@{vm.listData.size() == 0}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/animation_view" />
<TextView
android:id="@+id/txtEmptyState"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:breakStrategy="balanced"
android:gravity="center_horizontal"
android:text="@string/favorites_empty_state"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?android:textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/animation_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
+13 -26
View File
@@ -1,21 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="net.vonforst.evmap.viewmodel.FilterViewModel" />
<variable
name="vm"
type="FilterViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_container"
@@ -31,13 +20,11 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/filters_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:data="@{vm.filtersWithValue}"
tools:itemCount="3"
tools:listitem="@layout/item_filter_boolean" />
</LinearLayout>
</layout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/filters_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:itemCount="3"
tools:listitem="@layout/item_filter_boolean" />
</LinearLayout>
@@ -1,21 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="net.vonforst.evmap.viewmodel.FilterProfilesViewModel" />
<variable
name="vm"
type="FilterProfilesViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbar_container"
@@ -34,36 +23,33 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/filter_profiles_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
app:data="@{vm.filterProfiles}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
tools:itemCount="3"
tools:listitem="@layout/item_filter_boolean" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/filter_profiles_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
tools:itemCount="3"
tools:listitem="@layout/item_filter_boolean" />
<TextView
android:id="@+id/textView19"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:breakStrategy="balanced"
android:gravity="center_horizontal"
android:text="@string/filterprofiles_empty_state"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?android:textColorSecondary"
app:goneUnless="@{vm.filterProfiles.size() == 0}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<TextView
android:id="@+id/txtEmptyState"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:breakStrategy="balanced"
android:gravity="center_horizontal"
android:text="@string/filterprofiles_empty_state"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColor="?android:textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
+13 -42
View File
@@ -1,22 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="net.vonforst.evmap.viewmodel.MapViewModel" />
<import type="net.vonforst.evmap.viewmodel.Status" />
<import type="com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike" />
<variable
name="vm"
type="net.vonforst.evmap.viewmodel.MapViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -92,7 +77,7 @@
android:ellipsize="end"
android:background="@null"
android:gravity="center_vertical"
android:hint="@{vm.chargepoints.progress != null ? @string/downloading_chargers_percent(vm.chargepoints.progress * 100) : @string/search}"
android:hint="@string/search"
android:textSize="18sp"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textColorHint="@color/hint_text_color"
@@ -106,7 +91,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:invisibleUnless="@{search.text.length() > 0}"
android:visibility="invisible"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/handle"
@@ -131,11 +116,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="-10dp"
android:layout_marginBottom="-7dp"
android:indeterminate="@{ vm.chargepoints.progress == null }"
android:progress="@{ vm.chargepoints.progress != null ? Math.round(vm.chargepoints.progress * 100f) : 0 }"
android:indeterminate="true"
android:progress="0"
android:max="100"
android:visibility="visible"
app:goneUnless="@{ vm.chargepoints.status == Status.LOADING }" />
android:visibility="gone" />
</com.google.android.material.appbar.AppBarLayout>
@@ -153,15 +137,15 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gallery"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:data="@{vm.charger.data.photos}" />
android:layout_height="match_parent" />
<ImageView
android:id="@+id/gallery_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:srcCompat="@drawable/collage_bw"
app:invisibleUnless="@{vm.charger.data.photos == null || vm.charger.data.photos.size() == 0}" />
android:visibility="visible" />
</FrameLayout>
@@ -176,7 +160,6 @@
android:src="@drawable/ic_location"
app:backgroundTint="?android:colorBackground"
app:borderWidth="0dp"
app:isFabActive="@{ vm.myLocationEnabled }"
app:layout_behavior="@string/hide_on_scroll_fab_behavior" />
<androidx.core.widget.NestedScrollView
@@ -195,17 +178,7 @@
<include
android:id="@+id/detail_view"
layout="@layout/detail_view"
app:charger="@{vm.charger}"
app:availability="@{vm.availability}"
app:filteredAvailability="@{vm.filteredAvailability}"
app:predictionData="@{vm.predictionData}"
app:chargeCards="@{vm.chargeCardMap}"
app:filteredChargeCards="@{vm.filteredChargeCards}"
app:distance="@{vm.chargerDistance}"
app:expanded="@{vm.bottomSheetExpanded}"
app:apiName="@{vm.apiName}"
app:teslaPricing="@{vm.teslaPricing}" />
layout="@layout/detail_view" />
</androidx.core.widget.NestedScrollView>
@@ -253,7 +226,7 @@
android:layout_height="16dp"
android:background="?android:colorBackground"
android:layout_gravity="bottom"
app:invisibleUnless="@{vm.bottomSheetState == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED}"
android:visibility="invisible"
tools:visibility="invisible" />
<androidx.cardview.widget.CardView
@@ -269,8 +242,6 @@
<include
android:id="@+id/layers"
layout="@layout/map_layers"
app:vm="@{vm}" />
layout="@layout/map_layers" />
</androidx.cardview.widget.CardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
@@ -1,23 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.text.util.Linkify" />
<import type="net.vonforst.evmap.adapter.PlaceAutocompleteAdapterKt" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<variable
name="item"
type="net.vonforst.evmap.autocomplete.AutocompletePlace" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground">
<TextView
android:id="@+id/title"
@@ -29,7 +16,6 @@
android:layout_marginBottom="14dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@{item.primaryText}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -44,15 +30,11 @@
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@{item.secondaryText}"
android:padding="6dp"
android:background="@drawable/circle_bg_autocomplete"
android:backgroundTintMode="src_in"
app:imageTintActive="@{PlaceAutocompleteAdapterKt.isSpecialPlace(item.types)}"
app:backgroundTintActive="@{PlaceAutocompleteAdapterKt.isSpecialPlace(item.types)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@{PlaceAutocompleteAdapterKt.iconForPlaceType(item.types)}"
tools:srcCompat="@drawable/ic_address"
tools:tint="?colorControlNormal"
tools:backgroundTint="?colorControlNormal" />
@@ -62,9 +44,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{BindingAdaptersKt.distance(item.distanceMeters, context)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.distanceMeters != null}"
app:layout_constraintEnd_toEndOf="@+id/icon"
app:layout_constraintStart_toStartOf="@+id/icon"
app:layout_constraintTop_toBottomOf="@+id/icon"
@@ -79,13 +59,10 @@
android:layout_marginBottom="14dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@{item.secondaryText}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Lorem ipsum" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
+55 -80
View File
@@ -1,88 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless">
<data>
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:tintMode="src_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:tint="@color/available"
tools:srcCompat="@drawable/ic_connector_typ2" />
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
<import type="net.vonforst.evmap.adapter.ConnectorAdapter.ChargepointWithAvailability" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<variable
name="item"
type="ChargepointWithAvailability" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
<TextView
android:id="@+id/txtNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless">
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:visibility="gone"
tools:text="×99" />
<ImageView
android:id="@+id/imageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:tintMode="src_in"
android:contentDescription="@{item.chargepoint.type}"
app:connectorIcon="@{item.chargepoint.type}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tintAvailability="@{item.status}"
tools:tint="@color/available"
tools:srcCompat="@drawable/ic_connector_typ2" />
<TextView
android:id="@+id/txtStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;\u00D7 %d&quot;, item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
app:goneUnless="@{item.status == null}"
tools:visibility="gone"
tools:text="×99" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="30dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;%s/%d&quot;, BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:textColor="@android:color/white"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
app:goneUnless="@{item.status != null}"
app:backgroundTintAvailability="@{item.status}"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:text="@{item.chargepoint.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.chargepoint.hasKnownPower()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:textColorAvailability="@{item.status}"
tools:textColor="@color/available"
tools:text="350 kW" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<TextView
android:id="@+id/txtPower"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:textColor="@color/available"
tools:text="350 kW" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,33 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<net.vonforst.evmap.ui.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="net.vonforst.evmap.model.Chargepoint" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
<variable
name="item"
type="Chargepoint" />
<variable
name="enabled"
type="boolean" />
</data>
<net.vonforst.evmap.ui.CheckableConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_outline"
android:foreground="?selectableItemBackground"
android:clickable="@{enabled}"
android:focusable="true"
android:layout_margin="4dp">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_outline"
android:foreground="?selectableItemBackground"
android:focusable="true"
android:layout_margin="4dp">
<ImageView
android:id="@+id/imageView"
@@ -37,12 +17,9 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:tintMode="src_in"
android:contentDescription="@{item.type}"
app:connectorIcon="@{item.type}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
tools:tint="?colorControlNormal"
tools:srcCompat="@drawable/ic_connector_typ2" />
@@ -53,9 +30,7 @@
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:layout_marginEnd="4dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;× %d&quot;, item.count)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"
@@ -67,14 +42,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:text="@{item.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="350 kW" />
</net.vonforst.evmap.ui.CheckableConstraintLayout>
</layout>
</net.vonforst.evmap.ui.CheckableConstraintLayout>
+44 -63
View File
@@ -1,70 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<import type="android.text.util.Linkify" />
<variable
name="item"
type="net.vonforst.evmap.adapter.DetailsAdapter.Detail" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
<TextView
android:id="@+id/txtTariff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clickable="@{item.clickable}"
app:selectableItemBackground="@{item.clickable}">
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imgIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<TextView
android:id="@+id/txtTariff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:text="@{item.text}"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<ImageView
android:id="@+id/imgIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:tint="?colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_address" />
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@{item.contentDescription}"
app:tint="?colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@{item.icon}"
tools:srcCompat="@drawable/ic_address" />
<TextView
android:id="@+id/txtProvider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
tools:text="Lorem ipsum" />
<TextView
android:id="@+id/txtProvider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="14dp"
android:text="@{item.detailText}"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:linkify="@{item.links ? Linkify.WEB_URLS | Linkify.PHONE_NUMBERS : 0}"
app:goneUnless="@{item.detailText != null}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
tools:text="Lorem ipsum" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,202 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<import type="android.text.util.Linkify" />
<import type="java.time.DayOfWeek" />
<import type="android.transition.TransitionManager" />
<variable
name="item"
type="net.vonforst.evmap.adapter.DetailsAdapter.Detail" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container"
android:layout_width="match_parent"
<TextView
android:id="@+id/txtTariff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:clickable="@{item.clickable}"
app:selectableItemBackground="@{item.clickable}">
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="14dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/expandToggle"
app:layout_constraintStart_toEndOf="@+id/imageView3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<TextView
android:id="@+id/txtTariff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="14dp"
android:text="@{item.text}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/expandToggle"
app:layout_constraintStart_toEndOf="@+id/imageView3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:tint="?colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_address" />
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@{item.contentDescription}"
app:tint="?colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@{item.icon}"
tools:srcCompat="@drawable/ic_address" />
<TextView
android:id="@+id/txtProvider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/expandToggle"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<TextView
android:id="@+id/txtProvider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
app:linkify="@{item.links ? Linkify.WEB_URLS | Linkify.PHONE_NUMBERS : 0}"
android:text="@{item.detailText}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.detailText != null}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/expandToggle"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
app:layout_constraintVertical_bias="0.0"
tools:text="Lorem ipsum" />
<include
android:id="@+id/hours_mon"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtProvider" />
<include
android:id="@+id/hours_mon"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:dayOfWeek="@{DayOfWeek.MONDAY}"
app:goneUnless="@{expandToggle.checked}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@+id/txtProvider" />
<include
android:id="@+id/hours_tue"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_mon" />
<include
android:id="@+id/hours_tue"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.TUESDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_mon" />
<include
android:id="@+id/hours_wed"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_tue" />
<include
android:id="@+id/hours_wed"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.WEDNESDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_tue" />
<include
android:id="@+id/hours_thu"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_wed" />
<include
android:id="@+id/hours_thu"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.THURSDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_wed" />
<include
android:id="@+id/hours_fri"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_thu" />
<include
android:id="@+id/hours_fri"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.FRIDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_thu" />
<include
android:id="@+id/hours_sat"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_fri" />
<include
android:id="@+id/hours_sat"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.SATURDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_fri" />
<include
android:id="@+id/hours_sun"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_sat" />
<include
android:id="@+id/hours_sun"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{DayOfWeek.SUNDAY}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_sat" />
<include
android:id="@+id/hours_holiday"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_sun" />
<include
android:id="@+id/hours_holiday"
layout="@layout/item_detail_openinghours_item"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:goneUnless="@{expandToggle.checked}"
app:dayOfWeek="@{null}"
app:hours="@{item.hoursDays}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/txtTariff"
app:layout_constraintTop_toBottomOf="@id/hours_sun" />
<ToggleButton
android:id="@+id/expandToggle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="@dimen/expand_toggle_padding_small"
android:layout_marginEnd="16dp"
android:background="@drawable/expand_toggle"
android:textOff=""
android:textOn=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
android:id="@+id/expandToggle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="@{item.detailText != null ? @dimen/expand_toggle_padding_large : @dimen/expand_toggle_padding_small}"
android:layout_marginEnd="16dp"
android:background="@drawable/expand_toggle"
android:onClick="@{() -> TransitionManager.beginDelayedTransition(container)}"
android:textOff=""
android:textOn=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,48 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<TextView
android:id="@+id/txtDay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Montag" />
<import type="java.time.format.TextStyle" />
<import type="java.util.Locale" />
<variable
name="hours"
type="net.vonforst.evmap.model.OpeningHoursDays" />
<variable
name="dayOfWeek"
type="java.time.DayOfWeek" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView24"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{dayOfWeek != null ? dayOfWeek.getDisplayName(TextStyle.FULL, context.resources.configuration.locale) : @string/holiday}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Montag" />
<TextView
android:id="@+id/textView25"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:text="@{hours.getHoursForDayOfWeek(dayOfWeek) == null ? @string/closed_unfmt : hours.getHoursForDayOfWeek(dayOfWeek).toString()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="07:00-21:00" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<TextView
android:id="@+id/txtHours"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="07:00-21:00" />
</androidx.constraintlayout.widget.ConstraintLayout>
+16 -46
View File
@@ -1,28 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="net.vonforst.evmap.api.UtilsKt" />
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
<import type="net.vonforst.evmap.viewmodel.Status" />
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
<import type="net.vonforst.evmap.api.ChargepointApiKt" />
<variable
name="item"
type="net.vonforst.evmap.viewmodel.FavoritesViewModel.FavoritesListItem" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/background"
@@ -51,35 +32,32 @@
android:background="@drawable/selectable_opaque_background">
<TextView
android:id="@+id/textView15"
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@{item.charger.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:maxLines="2"
android:ellipsize="end"
android:hyphenationFrequency="normal"
app:layout_constraintEnd_toStartOf="@+id/textView16"
app:layout_constraintEnd_toStartOf="@+id/txtDistance"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textAlignment="viewStart"
tools:text="Nikola-Tesla-Parkhaus mit extra langem Namen, der auf mehrere Zeilen umbricht" />
<TextView
android:id="@+id/textView2"
android:id="@+id/txtAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@{item.charger.address.toString()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textAlignment="viewStart"
app:invisibleUnless="@{item.charger.address != null}"
app:layout_constraintEnd_toStartOf="@+id/textView7"
app:layout_constraintEnd_toStartOf="@+id/txtStatus"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView15"
app:layout_constraintTop_toBottomOf="@+id/txtName"
tools:text="Beispielstraße 10, 12345 Berlin" />
<TextView
@@ -89,50 +67,43 @@
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@{item.charger.formatChargepoints(ChargepointApiKt.stringProvider(context), LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textAlignment="viewStart"
app:layout_constraintEnd_toStartOf="@+id/textView7"
app:layout_constraintStart_toStartOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintEnd_toStartOf="@+id/txtStatus"
app:layout_constraintStart_toStartOf="@+id/txtAddress"
app:layout_constraintTop_toBottomOf="@+id/txtAddress"
tools:text="2x Typ 2 22 kW" />
<TextView
android:id="@+id/textView16"
android:id="@+id/txtDistance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@{BindingAdaptersKt.distance(item.distance, context)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:goneUnless="@{item.distance != null}"
app:layout_constraintEnd_toStartOf="@id/btnDelete"
app:layout_constraintTop_toTopOf="parent"
tools:text="9999,9 km" />
<TextView
android:id="@+id/textView7"
android:id="@+id/txtStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_rect"
android:padding="2dp"
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), &quot;%s/%d&quot;, BindingAdaptersKt.availabilityText(item.available.data), item.total)}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="@android:color/white"
app:backgroundTintAvailability="@{item.available.data}"
app:invisibleUnless="@{item.available.status == Status.SUCCESS}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnDelete"
tools:backgroundTint="@color/available"
tools:text="80/99" />
<ProgressBar
android:id="@+id/progressBar4"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
app:goneUnless="@{item.available.status == Status.LOADING}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnDelete" />
@@ -149,5 +120,4 @@
android:contentDescription="@string/delete" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</layout>
</FrameLayout>
+29 -48
View File
@@ -1,53 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<TextView
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/toggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Free charging" />
<import type="net.vonforst.evmap.model.BooleanFilter" />
<Switch
android:id="@+id/toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<import type="net.vonforst.evmap.model.BooleanFilterValue" />
<import type="net.vonforst.evmap.model.FilterWithValue" />
<variable
name="item"
type="FilterWithValue&lt;BooleanFilterValue&gt;" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView17"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@{item.filter.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/switch1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Free charging" />
<Switch
android:id="@+id/switch1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:checked="@={item.value.value}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,85 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<TextView
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Connectors" />
<import type="net.vonforst.evmap.model.MultipleChoiceFilter" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:animateLayoutChanges="true"
app:chipSpacingVertical="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnAll">
<import type="net.vonforst.evmap.model.MultipleChoiceFilterValue" />
<import type="net.vonforst.evmap.model.FilterWithValue" />
<variable
name="item"
type="FilterWithValue&lt;MultipleChoiceFilterValue&gt;" />
<variable
name="showingAll"
type="boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView17"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@{item.filter.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Connectors" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:animateLayoutChanges="true"
app:chipSpacingVertical="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnAll">
<com.google.android.material.chip.Chip
android:id="@+id/chipMore"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{showingAll ? @string/show_less : @string/show_more}"
app:chipMinTouchTargetSize="40dp" />
</com.google.android.material.chip.ChipGroup>
<Button
android:id="@+id/btnAll"
style="@style/Widget.Material3.Button.TextButton.Dialog"
<com.google.android.material.chip.Chip
android:id="@+id/chipMore"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all"
app:layout_constraintBaseline_toBaselineOf="@+id/textView17"
app:layout_constraintEnd_toStartOf="@+id/btnNone" />
android:text="@string/show_more"
app:chipMinTouchTargetSize="40dp" />
</com.google.android.material.chip.ChipGroup>
<Button
android:id="@+id/btnNone"
style="@style/Widget.Material3.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/none"
app:layout_constraintBaseline_toBaselineOf="@+id/textView17"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnAll"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all"
app:layout_constraintBaseline_toBaselineOf="@+id/txtName"
app:layout_constraintEnd_toStartOf="@+id/btnNone" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<Button
android:id="@+id/btnNone"
style="@style/Widget.Material3.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/none"
app:layout_constraintBaseline_toBaselineOf="@+id/txtName"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,72 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<TextView
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Networks" />
<import type="net.vonforst.evmap.model.MultipleChoiceFilter" />
<ImageButton
android:id="@+id/btnEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="?selectableItemBackgroundBorderless"
android:padding="8dp"
android:contentDescription="@string/edit"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_edit" />
<import type="net.vonforst.evmap.model.MultipleChoiceFilterValue" />
<TextView
android:id="@+id/txtDetail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:textAlignment="viewStart"
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtName"
tools:text="4 selected" />
<import type="net.vonforst.evmap.model.FilterWithValue" />
<variable
name="item"
type="FilterWithValue&lt;MultipleChoiceFilterValue&gt;" />
<variable
name="showingAll"
type="boolean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView17"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="@{item.filter.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:textAlignment="viewStart"
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Networks" />
<ImageButton
android:id="@+id/btnEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="?selectableItemBackgroundBorderless"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_edit"
android:contentDescription="@string/edit" />
<TextView
android:id="@+id/textView26"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:text="@{item.value.all ? @string/all_selected : @string/number_selected(item.value.values.size())}"
android:textAlignment="viewStart"
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView17"
tools:text="4 selected" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
+78 -91
View File
@@ -1,99 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="item"
type="net.vonforst.evmap.storage.FilterProfile" />
</data>
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/delete_red"> <!--Add your background color here-->
<ImageView
android:id="@+id/delete_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical|end"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:tint="@android:color/white"
app:srcCompat="@drawable/ic_delete" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/foreground"
android:layout_width="match_parent"
android:layout_height="?listPreferredItemHeight"
android:background="?android:colorBackground">
<TextView
android:id="@+id/textView9"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:text="@{item.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnRename"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.511"
tools:text="Lorem ipsum" />
<ImageButton
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/handle"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_delete"
android:contentDescription="@string/delete" />
<ImageButton
android:id="@+id/btnRename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnDelete"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_edit"
android:contentDescription="@string/rename" />
<ImageView
android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:contentDescription="@string/reorder"
android:scaleType="center"
android:src="@drawable/ic_reorder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="match_parent"
android:background="@color/delete_red">
<ImageView
android:id="@+id/delete_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical|end"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:tint="@android:color/white"
app:srcCompat="@drawable/ic_delete" />
</FrameLayout>
</layout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/foreground"
android:layout_width="match_parent"
android:layout_height="?listPreferredItemHeight"
android:background="?android:colorBackground">
<TextView
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnRename"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.511"
tools:text="Lorem ipsum" />
<ImageButton
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/delete"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/handle"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_delete" />
<ImageButton
android:id="@+id/btnRename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/rename"
app:tint="?colorControlNormal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnDelete"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_edit" />
<ImageView
android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:contentDescription="@string/reorder"
android:scaleType="center"
android:src="@drawable/ic_reorder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
+37 -66
View File
@@ -1,72 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<data>
<TextView
android:id="@+id/txtName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Minimum power" />
<import type="net.vonforst.evmap.model.SliderFilter" />
<SeekBar
android:id="@+id/seekBar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/txtValue"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtName" />
<import type="net.vonforst.evmap.model.SliderFilterValue" />
<TextView
android:id="@+id/txtValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@+id/seekBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/seekBar"
tools:text="0 kW" />
<import type="net.vonforst.evmap.model.FilterWithValue" />
<variable
name="item"
type="FilterWithValue&lt;SliderFilterValue&gt;" />
<variable
name="progress"
type="int" />
<variable
name="mappedValue"
type="int" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView17"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@{item.filter.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Minimum power" />
<SeekBar
android:id="@+id/seekBar"
style="@style/Widget.AppCompat.SeekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:max="@{((SliderFilter) item.filter).max - ((SliderFilter) item.filter).min}"
android:progress="@={progress}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textView18"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView17" />
<TextView
android:id="@+id/textView18"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@{String.valueOf(mappedValue) + ' ' + ((SliderFilter) item.filter).unit}"
app:layout_constraintBottom_toBottomOf="@+id/seekBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/seekBar"
tools:text="0 kW" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</androidx.constraintlayout.widget.ConstraintLayout>
+45 -69
View File
@@ -1,24 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="net.vonforst.evmap.viewmodel.MapViewModel" />
<import type="com.car2go.maps.AnyMap" />
<variable
name="vm"
type="MapViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
@@ -51,8 +37,6 @@
android:id="@+id/rbStandard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{vm.mapType.equals(AnyMap.Type.NORMAL)}"
android:onClick="@{() -> vm.setMapType(AnyMap.Type.NORMAL)}"
android:text="@string/map_type_normal"
android:textAlignment="viewStart" />
@@ -60,8 +44,6 @@
android:id="@+id/rbSatellite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{vm.mapType.equals(AnyMap.Type.HYBRID)}"
android:onClick="@{() -> vm.setMapType(AnyMap.Type.HYBRID)}"
android:text="@string/map_type_satellite"
android:textAlignment="viewStart" />
@@ -69,54 +51,48 @@
android:id="@+id/rbTerrain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="@{vm.mapType.equals(AnyMap.Type.TERRAIN)}"
android:onClick="@{() -> vm.setMapType(AnyMap.Type.TERRAIN)}"
android:text="@string/map_type_terrain"
android:textAlignment="viewStart" />
</RadioGroup>
<TextView
android:id="@+id/textView23"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@string/map_details"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textAlignment="viewStart"
app:goneUnless="@{vm.mapTrafficSupported}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" />
<TextView
android:id="@+id/textView23"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@string/map_details"
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" />
<CheckBox
android:id="@+id/cbTraffic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/map_traffic"
android:checked="@={vm.mapTrafficEnabled}"
android:textAlignment="viewStart"
app:goneUnless="@{vm.mapTrafficSupported}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView23" />
<CheckBox
android:id="@+id/cbTraffic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/map_traffic"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView23" />
<ImageButton
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginEnd="12dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close"
app:tint="?colorControlNormal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<ImageButton
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginEnd="12dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close"
app:tint="?colorControlNormal" />
</androidx.constraintlayout.widget.ConstraintLayout>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/aboutlibraries,
@string/aboutLibraries_showLicense,
@string/aboutLibraries_showVersion,
@string/aboutLibraries_description_name,
@string/aboutLibraries_description_showIcon,
@string/aboutLibraries_description_showVersion" />
+31
View File
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
+14
View File
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----
+400
View File
@@ -0,0 +1,400 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EVMap</string>
<string name="title_activity_maps">EVMap</string>
<string name="connectors">Connectors</string>
<string name="no_maps_app_found">Instal·leu primer una aplicació de navegació</string>
<string name="no_browser_app_found">Instal·leu primer un navegador web</string>
<string name="no_email_app_found">Instal·leu primer una aplicació de correu</string>
<string name="no_share_app_found">No s\'ha pogut trobar cap aplicació amb la qual compartir</string>
<string name="system_setting_not_supported">El vostre sistema no admet aquesta configuració</string>
<string name="address">Adreça</string>
<string name="operator">Operador</string>
<string name="network">Xarxa</string>
<string name="hours">Horaris d\'obertura</string>
<string name="open_247"><![CDATA[<b>Obert 24/7</b>]]></string>
<string name="closed"><![CDATA[<b>Tancat</b>]]></string>
<string name="open_closesat"><![CDATA[<b>Obert</b> · Tanca a %s]]></string>
<string name="closed_opensat"><![CDATA[<b>Tancat</b> · Obre a %s]]></string>
<string name="closed_unfmt">Tancat</string>
<string name="holiday">Festiu</string>
<string name="cost">Cost</string>
<string name="cost_detail"><![CDATA[<b>Recàrrega:</b> %1$s · <b>Aparcament:</b> %2$s]]></string>
<string name="cost_detail_charging"><![CDATA[<b>Recàrrega %s </b>]]></string>
<string name="cost_detail_parking"><![CDATA[<b>Aparcament %s</b>]]></string>
<string name="charging_free">de franc</string>
<string name="parking_free">de franc</string>
<string name="general_info">Informació general</string>
<string name="realtime_data_unavailable">Estat en temps real no disponible</string>
<string name="realtime_data_login_needed">Compte Tesla necessari per a les dades en temps real</string>
<string name="realtime_data_loading">S\'està comprovant l\'estat en temps real…</string>
<string name="search">Cerca</string>
<string name="menu_map">Mapa</string>
<string name="menu_favs">Preferits</string>
<string name="menu_filter">Filtre</string>
<string name="not_implemented">encara no s\'ha implementat</string>
<string name="about">Quant a</string>
<string name="version">Versió</string>
<string name="github_link_title">Codi font</string>
<string name="oss_licenses">Llicències</string>
<string name="settings">Configuració</string>
<string name="settings_ui">Interfície</string>
<string name="settings_map">Mapa</string>
<string name="copyright">Copyright</string>
<string name="other">Altres</string>
<string name="privacy">Privadesa</string>
<string name="fav_add">Desa com a preferit</string>
<string name="fav_remove">Suprimeix dels preferits</string>
<string name="pref_navigate_use_maps">Navegació immediata</string>
<string name="pref_navigate_use_maps_on">El botó de navegació inicia la guia de ruta amb Google Maps</string>
<string name="pref_navigate_use_maps_off">El botó de navegació obre l\'aplicació de mapes amb la ubicació del carregador</string>
<string name="coordinates">Coordenades</string>
<string name="share">Comparteix</string>
<string name="filter_free">Només carregadors gratuïts</string>
<string name="filter_min_power">Potència mínima</string>
<string name="filter_free_parking">Només carregadors amb aparcament gratuït</string>
<string name="filter_min_connectors">Nombre mínim de connectors</string>
<string name="filter_connectors">Connectors</string>
<string name="plug_type_1">Tipus 1</string>
<string name="plug_type_2">Tipus 2</string>
<string name="plug_type_3a">Tipus 3A</string>
<string name="plug_type_3c">Tipus 3C</string>
<string name="plug_ccs">CCS</string>
<string name="plug_schuko">Schuko</string>
<string name="plug_chademo">CHADEMO</string>
<string name="plug_cee_blau">CEE Blau</string>
<string name="plug_cee_rot">CEE Vermell</string>
<string name="plug_roadster_hpc">Tesla Roadster (2008) HPC</string>
<string name="plug_mcs">MCS</string>
<string name="all">tots</string>
<string name="none">cap</string>
<string name="show_more">més…</string>
<string name="show_less">menys…</string>
<string name="favorites_empty_state">Els carregadors desats es mostren aquí</string>
<string name="donation_successful">Gràcies</string>
<string name="map_type_normal">Per defecte</string>
<string name="map_type_satellite">Satèl·lit</string>
<string name="map_type_terrain">Terreny</string>
<string name="map_type">Tipus de mapa</string>
<string name="map_details">Detalls del mapa</string>
<string name="map_traffic">Trànsit</string>
<string name="faq">Preguntes freqüents</string>
<string name="menu_filters_active">Filtres actius</string>
<string name="filters_activated">Filtres activats</string>
<string name="filters_deactivated">Filtres desactivats</string>
<string name="menu_edit_filters">Edita els filtres</string>
<string name="menu_manage_filter_profiles">Gestiona els perfils de filtre</string>
<string name="go_to_chargeprice">Compara els preus</string>
<string name="filter_networks">Xarxes</string>
<string name="filter_operators">Operadors</string>
<string name="filter_chargecards">Formes de pagament</string>
<string name="number_selected">%d seleccionats</string>
<string name="edit">edita</string>
<string name="cancel">Cancel·la</string>
<string name="ok">D\'acord</string>
<string name="pref_language">Llengua de l\'aplicació</string>
<string name="pref_darkmode">Mode fosc</string>
<string name="connection_error">No s\'han pogut carregar les estacions de recàrrega</string>
<string name="location_error">No s\'ha pogut detectar la ubicació. Comproveu la configuració del sistema</string>
<string name="filter_open_247">Disponible 24/7</string>
<string name="filter_barrierfree">Usable sense registre</string>
<string name="filter_exclude_faults">Exclou els carregadors amb fallades denunciades</string>
<string name="pref_map_provider">Proveïdor de mapes</string>
<string name="twitter">Twitter</string>
<string name="mastodon">Mastodon</string>
<string name="goingelectric_forum">Fil del fòrum a GoingElectric.de</string>
<string name="tff_forum">Fil del fòrum a TFF-Forum.de</string>
<string name="contact">Contacte</string>
<string name="menu_report_new_charger">Carregador nou</string>
<string name="edit_at_datasource">Edita a %s</string>
<string name="categories">Categories</string>
<string name="category_service_on_motorway">Zona de servei (a l\'autopista)</string>
<string name="category_service_off_motorway">Zona de servei (fora de l\'autopista)</string>
<string name="category_railway_station">Estació de ferrocarril</string>
<string name="category_public_authorities">Administracions públiques</string>
<string name="category_camping">Lloc d\'acampada</string>
<string name="category_shopping_mall">Centre comercial</string>
<string name="category_airport">Aeroport</string>
<string name="category_amusement_park">Parc d\'atraccions</string>
<string name="category_hotel">Hotel</string>
<string name="category_cinema">Cinema</string>
<string name="category_church">Església</string>
<string name="category_hospital">Hospital</string>
<string name="category_museum">Museu</string>
<string name="category_private_charger">Carregador privat</string>
<string name="category_restaurant">Restaurant</string>
<string name="category_swimming_pool">Piscina</string>
<string name="category_supermarket">Supermercat</string>
<string name="category_parking_underground">Aparcament subterrani</string>
<string name="category_zoo">Zoo</string>
<string name="menu_apply">Aplica els filtres</string>
<string name="menu_save_profile">Desa com a perfil</string>
<string name="menu_reset">Restableix la configuració del filtre</string>
<string name="no_filters">Sense filtres</string>
<string name="filter_custom">Filtre modificat</string>
<string name="filter_favorites">Preferits</string>
<string name="reorder">reordena</string>
<string name="delete">Suprimeix</string>
<string name="auto_chargers_closeby">Carregadors a prop</string>
<string name="auto_favorites">Preferits</string>
<string name="auto_charging_level">Nivell de recàrrega</string>
<string name="auto_no_data">No disponible</string>
<string name="auto_speed">Velocitat</string>
<string name="auto_settings">Configuració</string>
<string name="referral_tesla">Tesla</string>
<string name="generic_connection_error">No s\'han pogut carregar les dades</string>
<string name="copied">S\'ha copiat al porta-retalls</string>
<string name="downloading_chargers_percent">S\'està baixant... %.0f%%</string>
<string name="status_available">Disponible</string>
<string name="status_occupied">Ocupat</string>
<string name="status_faulted">Fora de servei</string>
<string name="status_unknown">Estat desconegut</string>
<string name="status_since">%1$s des de %2$s</string>
<string name="charger_name">Nom del carregador</string>
<string name="pref_chargeprice_native_integration">Comparació de preus amb EVMap</string>
<string name="accessibility_public">Públic</string>
<string name="accessibility_visitors">Visitants</string>
<string name="accessibility_employees">Treballadors</string>
<string name="accessibility_by_appointment">Amb cita prèvia</string>
<string name="accessibility_residents">Residents</string>
<string name="chargeprice_removal_2025_dialog_title">Ho sento!</string>
<string name="category_rest_area">Àrea de descans</string>
<string name="category_petrol_station">Gasolinera</string>
<string name="save_as_profile">Desa com a perfil</string>
<string name="save_profile_enter_name">Introduïu el nom del perfil del filtre:</string>
<string name="filterprofile_name_not_unique">Ja hi ha un perfil de filtre amb aquest nom</string>
<string name="welcome_to_evmap">Benvingut a l\'EVMap</string>
<string name="welcome_1">Trobeu els carregadors de vehicles elèctrics al vostre voltant</string>
<string name="welcome_2_title">Teniu el poder</string>
<string name="welcome_2">El color de cada carregador correspon a la seva potència màxima de recàrrega</string>
<string name="welcome_2_detail">Això també es pot veure a «Quant a» → «Preguntes freqüents»</string>
<string name="donation_dialog_title">Gràcies per usar l\'EVMap</string>
<string name="donation_dialog_detail">L\'EVMap és de codi obert i gratuït. Les contribucions de codi a GitHub són molt apreciades. Per a ajudar a cobrir els costos d\'execució per a l\'accés a les dades, considereu donar una quantitat de la vostra elecció al desenvolupador.</string>
<string name="chargeprice_donation_dialog_detail">Feu un gran ús de la funció de comparació de preus. Ajudeu a cobrir els costos d\'aquestes dades donant suport a l\'EVMap amb un donatiu.</string>
<string name="deleted_item">S\'ha suprimit «%s»</string>
<string name="undo">Desfés</string>
<string name="rename">Canvia el nom</string>
<string name="charging_barrierfree">Usable sense registre</string>
<plurals name="charge_cards_compatible_num">
<item quantity="one">%d mètode de pagament compatible</item>
<item quantity="many">%d de mètodes de pagament compatibles</item>
<item quantity="other">%d mètodes de pagament compatibles</item>
</plurals>
<string name="navigate">Navega</string>
<string name="verified">verificat</string>
<string name="charge_price_format">%2$s%1$.2f</string>
<string name="charge_price_average_format">⌀ %1$.2f %2$s/kWh</string>
<string name="charge_price_kwh_format">%1$.2f %2$s/kWh</string>
<string name="charge_price_minute_format">%2$s%1$.2f/min</string>
<string name="chargeprice_select_connector">Trieu un connector</string>
<string name="edit_on_goingelectric_info">Inicieu sessió a GoingElectric.de si aquesta pàgina és buida</string>
<string name="percent_format">%.0f%%</string>
<string name="chargeprice_per_kwh">per kWh</string>
<string name="chargeprice_per_minute">per min</string>
<string name="chargeprice_blocking_fee">S\'està blocant la comissió ing%s</string>
<string name="chargeprice_no_tariffs_found">No hi ha plans de recàrrega per aquest carregador a Chargeprice.app</string>
<string name="chargeprice_min_spend">Despesa mínima: %2$s%1$.2f/mes</string>
<string name="settings_chargeprice">Comparació de preus</string>
<string name="pref_my_vehicle">Els meus vehicles</string>
<string name="chargeprice_select_car_first">Trieu primer el model de cotxe a la configuració</string>
<string name="chargeprice_battery_range_from">Recàrrega des de</string>
<string name="chargeprice_battery_range_to">a</string>
<string name="chargeprice_stats">(%1$.0f kWh, aprox. %2$s, , %3$.0f kW)</string>
<string name="chargeprice_vehicle">Vehicle</string>
<string name="chargeprice_price_not_available">Preu no disponible</string>
<string name="close">Tanca</string>
<string name="chargeprice_title">Preus</string>
<string name="chargeprice_connection_error">No s\'han pogut carregar els preus</string>
<string name="chargeprice_no_compatible_connectors">No hi ha connectors compatibles en aquesta estació de recàrrega</string>
<string name="pref_chargeprice_currency">Moneda</string>
<string name="pref_my_tariffs">Els meus plans de recàrrega</string>
<string name="chargeprice_all_tariffs_selected">tots els plans seleccionats</string>
<string name="license">Llicència</string>
<string name="settings_charger_data">Estacions de recàrrega</string>
<string name="pref_data_source">Font de dades</string>
<plurals name="chargeprice_some_tariffs_selected">
<item quantity="one">%d pla seleccionat</item>
<item quantity="many">%d de plans seleccionats</item>
<item quantity="other">%d plans seleccionats</item>
</plurals>
<string name="unknown_operator">Operador desconegut</string>
<string name="data_sources_description">Trieu una font de dades per a les estacions de recàrrega. Més tard es pot canviar a la configuració de l\'aplicació.</string>
<string name="data_source_goingelectric">GoingElectric.de</string>
<string name="data_source_nobil">NOBIL</string>
<string name="data_source_openstreetmap">OpenStreetMap</string>
<string name="data_source_openchargemap">Open Charge Map</string>
<string name="data_source_goingelectric_desc">Molt bo als països de parla alemanya. Descripcions en alemany. Mantingut a la comunitat.</string>
<string name="data_source_nobil_desc"><![CDATA[El govern obert i la comunitat van proporcionar dades a Suècia i Noruega.]]></string>
<string name="data_source_openchargemap_desc"><![CDATA[A tot el món, amb qualitats variables. Descripcions en anglès o en l\'idioma local. Dades de govern obertes i mantingudes per la comunitat en alguns països (per exemple, Amèrica del Nord, Regne Unit, França, Noruega).]]></string>
<string name="data_source_openstreetmap_desc">Suport experimental en EVMap, no totes les característiques són disponibles.</string>
<string name="next">següent</string>
<string name="get_started">Primers passos</string>
<string name="got_it">Entesos</string>
<string name="lets_go">Som-hi</string>
<string name="crash_report_text">L\'EVMap ha fallat. Envieu un informe de fallada al desenvolupador.</string>
<string name="crash_report_comment_prompt">Podeu afegir un comentari a continuació:</string>
<string name="powered_by_mapbox">funciona amb Mapbox</string>
<string name="pref_search_provider">Proveïdor de cerca</string>
<string name="pref_search_provider_info"><![CDATA[Les dades per a les cerques són cares d\'aconseguir, especialment des de Google Maps. Considereu de fer un donatiu a través de «Quant a» → «Fes un donatiu».]]></string>
<string name="github_sponsors">Patrocinadors a GitHub</string>
<string name="donate_desc">Doneu suport al desenvolupament de l\'EVMap amb una donatiu únic</string>
<string name="unnamed_filter_profile">Perfil de filtre sense nom</string>
<string name="privacy_link">https://ev-map.app/privacypolicy/</string>
<string name="faq_link">https://ev-map.app/faq/</string>
<string name="chargeprice_faq_link">https://ev-map.app/faq/-price-comparison-feature</string>
<string name="referral_link">https://ev-map.app/referrals/</string>
<string name="required">requerit</string>
<string name="edit_filter_profile">Edita «%s»</string>
<string name="pref_search_delete_recent">Suprimeix els resultats de cerca recents</string>
<string name="deleted_recent_search_results">S\'han eliminat els resultats de cerca recents</string>
<string name="settings_data_sources">Fonts de dades</string>
<string name="help">Ajuda</string>
<string name="settings_android_auto">Android Auto</string>
<string name="pref_chargeprice_allow_unbalanced_load_summary">Permet la càrrega de AC monofàsica amb més de 4,5 kW</string>
<string name="pref_map_rotate_gestures_enabled">Gir del mapa</string>
<string name="pref_map_rotate_gestures_on">Useu dos dits per a girar el mapa</string>
<string name="pref_map_rotate_gestures_off">Gir desactivat (nord sempre cap amunt)</string>
<string name="autocomplete_connection_error">No s\'han pogut carregar els suggeriments</string>
<string name="pref_language_device_default">Dispositiu per defecte</string>
<string name="pref_darkmode_device_default">Dispositiu per defecte</string>
<string name="pref_darkmode_always_on">sempre actiu</string>
<string name="pref_darkmode_always_off">sempre desactivat</string>
<string name="pref_provider_google_maps">Google Maps</string>
<string name="pref_provider_osm">OpenStreetMap</string>
<string name="pref_provider_osm_mapbox">OpenStreetMap (Mapbox)</string>
<string name="about_contributors">Col·laboradors</string>
<string name="about_contributors_text">Gràcies a tots els col·laboradors per les contribucions de codi i traducció a l\'EVMap:</string>
<string name="utilization_prediction">Predicció d\'ús</string>
<string name="powered_by_fronyx">funciona amb fronyx</string>
<string name="prediction_help">La predicció es basa en factors com el dia de la setmana, l\'hora del dia i l\'ús passat, de manera que es pot evitar carregadors superpoblats. No hi ha garantia.</string>
<string name="prediction_time_colon">%s:</string>
<plurals name="prediction_number_available">
<item quantity="one">%1$d/%2$d disponible</item>
<item quantity="many">%1$d/%2$d disponibles</item>
<item quantity="other">%1$d/%2$d disponibles</item>
</plurals>
<string name="pref_prediction_enabled">Mostra les prediccions d\'ús</string>
<string name="pref_prediction_enabled_summary">per als carregadors compatibles\n(actualment només DC a Alemanya)</string>
<string name="prediction_only">(%s només)</string>
<string name="prediction_dc_plugs_only">Endolls DC</string>
<string name="data_source_switched_to">Font de dades canviada a %s</string>
<string name="pref_applink_associate">Obre els enllaços admesos</string>
<string name="pref_applink_associate_summary">de goingelectric.de i openchargemap.org</string>
<string name="chargeprice_header_my_tariffs">Els meus itineraris</string>
<string name="chargeprice_header_other_tariffs">Altres plans</string>
<string name="developer_mode_enabled">Mode de desenvolupador activat</string>
<string name="developer_options">Opcions de desenvolupador</string>
<string name="disable_developer_mode">Desactiva el mode de desenvolupador</string>
<string name="developer_mode_disabled">Mode de desenvolupament desactivat</string>
<string name="gps">GPS</string>
<string name="compass">Brúixola</string>
<string name="charger_website">Lloc web</string>
<string name="location_status">Estat del proveïdor d\'ubicació</string>
<string name="pref_tesla_account">Compte Tesla</string>
<string name="pref_tesla_account_enabled">Sessió iniciada com a %s</string>
<string name="pref_tesla_account_disabled">Inicieu la sessió per veure dades en temps real per als Supercarregadors Tesla. No cal vehicle Tesla</string>
<string name="logging_in">S\'està iniciant sessió…</string>
<string name="logged_out">Sessió finalitzada</string>
<string name="login">Inicia sessió</string>
<string name="login_error">Ha fallat l\'inici de sessió</string>
<string name="tesla_pricing_owners">Només vehicles Tesla:</string>
<string name="tesla_pricing_members">Vehicles Tesla i membres:</string>
<string name="tesla_pricing_others">Altres clients:</string>
<string name="pricing_up_to">fins a %s</string>
<string name="tesla_pricing_other_times">Altres vegades:</string>
<string name="average_utilization">Ús mitjà</string>
<string name="website">Lloc web</string>
<string name="pref_map_scale">Mostra la barra d\'escala del mapa</string>
<string name="pref_map_scale_meters_and_miles">Milles i metres a la barra d\'escala del mapa</string>
<string name="pref_units">Unitats</string>
<string name="pref_units_default">Dispositiu per defecte</string>
<string name="pref_units_metric">Mètric</string>
<string name="pref_units_imperial">Imperial</string>
<string name="data_retrieved_at">Dades recuperades %s</string>
<string name="settings_caching">Memòria cau</string>
<string name="settings_cache_count">Mida de la memòria cau</string>
<string name="settings_cache_clear">Neteja la memòria cau</string>
<string name="settings_cache_clear_summary">Suprimeix tots els carregadors de la memòria cau excepte els preferits</string>
<string name="settings_cache_count_summary">%1$d carregadors en la memòria cau, %2$.1f MB</string>
<string name="auto_location_service">L\'EVMap s\'està executant amb Android Auto i usa la vostra ubicació.</string>
<string name="auto_no_chargers_found">No s\'ha trobat cap carregador proper</string>
<string name="auto_no_favorites_found">No s\'ha trobat cap preferit</string>
<string name="auto_location_permission_needed">Per a executar l\'EVMap a Android Auto, heu de concedir accés a la vostra ubicació.</string>
<string name="auto_vehicle_data_permission_needed">Per a aquesta funció, l\'EVMap necessita accés a les dades del vehicle.</string>
<string name="auto_chargers_near_location">Prop de %s</string>
<string name="auto_no_refresh_possible">No és possible fer més actualitzacions. Torneu enrere i reinicieu.</string>
<string name="auto_vehicle_data">Dades del vehicle</string>
<string name="auto_range">Autonomia</string>
<string name="sounds_cool">Sona bé</string>
<string name="auto_chargeprice_vehicle_unavailable">L\'EVMap no ha pogut determinar el vostre model de vehicle.</string>
<string name="auto_chargeprice_vehicle_unknown">Cap dels vehicles seleccionats a l\'aplicació coincideix amb aquest vehicle (%1$s %2$s).</string>
<string name="loading">S\'està carregant…</string>
<string name="auto_multipage_goto">Pàgina %d</string>
<string name="auto_multipage">(%1$d/%2$d)</string>
<string name="reload">Torna a carregar</string>
<string name="accept_privacy"><![CDATA[He llegit i accepto la política de privacitat de l\'<a href="%s">EVMap</a>.]]></string>
<string name="referrals">Enllaços de referits</string>
<string name="referrals_info">També podeu usar un dels enllaços de referits a continuació per a donar suport al desenvolupador amb la vostra compra.</string>
<string name="pref_chargeprice_native_integration_on">Les dades de preus es mostraran directament a l\'EVMap</string>
<string name="pref_chargeprice_native_integration_off">El botó de comparació de preus farà referència a l\'aplicació o lloc web de Chargeprice</string>
<string name="chargeprice_removal_2025_dialog_detail">Els costos per a l\'accés a les dades de pagament han augmentat bruscament i no es poden cobrir amb donatius, de manera que l\'EVMap ja no pot mostrar aquestes dades directament. Per ara, això obre el lloc web de Chargeprice. S\'està desenvolupant una solució alternativa, però portarà temps i pot començar amb característiques limitades. Gràcies per la vostra paciència i suport!</string>
<string name="auto_use_new_map_screen">Nova pantalla del mapa (beta)</string>
<string name="auto_confirm_delete_profile">Voleu suprimir el perfil de filtre «%s»?</string>
<string name="realtime_data_source">Font d\'estat en temps real (beta): %s</string>
<string name="source">Font: %s</string>
<string name="plug_type_2_tethered">Cable tipus 2</string>
<string name="plug_supercharger">Supercarregador Tesla</string>
<string name="donation_failed">Hi ha hagut un problema</string>
<string name="fault_report">Informe de fallada</string>
<string name="fault_report_date">Informe de fallada (última actualització: %s)</string>
<string name="filter_accessibility">Accessibilitat del carregador</string>
<string name="charge_cards">Formes de pagament</string>
<string name="category_holiday_home">Casa de vacances</string>
<string name="category_caravan_site">Lloc de caravanes</string>
<string name="filterprofiles_empty_state">No teniu perfils de filtre desats</string>
<string name="verified_desc">Una vegada s\'ha confirmat que el carregador funciona per un membre de la comunitat %s</string>
<string name="powered_by_chargeprice">funciona amb Chargeprice</string>
<string name="chargeprice_battery_range">Recàrrega de %1$.0f%% a %2$.0f%%</string>
<plurals name="pref_my_tariffs_summary">
<item quantity="one">(es destacarà en la comparació de preus)</item>
<item quantity="many">(es destacaran en la comparació de preus)</item>
<item quantity="other">(es destacaran en la comparació de preus)</item>
</plurals>
<string name="pref_chargeprice_allow_unbalanced_load">Permet la càrrega sense balanceig</string>
<string name="refresh_live_data">actualitza l\'estat en temps real</string>
<string name="log_out">Surt</string>
<string name="auto_fault_report_date">⚠️ Informe de fallada (%s)</string>
<string name="auto_prices">Preu</string>
<string name="auto_chargers_ahead">Només carregadors en la direcció de conducció</string>
<string name="settings_android_auto_chargeprice_range">Interval de recàrrega per a la comparació de preus</string>
<string name="auto_zoom_for_details">Amplieu per a veure els detalls</string>
<string name="amenities">Serveis</string>
<string name="donate">Fes un donatiu</string>
<string name="retry">Torna a intentar-ho</string>
<string name="and_n_others">i %d altres</string>
<string name="category_car_dealership">Concessionari de cotxes</string>
<string name="category_parking">Aparcament</string>
<string name="chargeprice_donation_dialog_title">Sou un bon caçador de gangues!</string>
<string name="chargeprice_session_fee">taxa per sessió</string>
<string name="chargeprice_base_fee">Taxa base: %2$s%1$.2f/mes</string>
<string name="pref_chargeprice_no_base_fee">Exclou plans amb taxes mensuals</string>
<string name="pref_chargeprice_show_provider_customer_tariffs">Inclou plans vinculats</string>
<string name="pref_chargeprice_show_provider_customer_tariffs_summary">Algunes companyies de serveis ofereixen preus especials per als seus clients</string>
<string name="github_sponsors_desc">Doneu suport a l\'EVMap a GitHub Sponsors</string>
<string name="tesla_pricing_blocking_fee">Taxa de bloqueig: %s</string>
<string name="open_in_app">Obre en l\'aplicació</string>
<string name="opened_on_phone">S\'ha obert al telèfon</string>
<string name="grant_on_phone">Permet al telèfon</string>
<string name="auto_heading">Direcció</string>
<string name="welcome_android_auto">Suport d\'Android Auto</string>
<string name="selecting_all">tots els elements seleccionats</string>
<string name="selecting_none">cap element seleccionat</string>
<string name="status_charging">Recarregant</string>
<string name="chargeprice_provider_customer_tariff">Només per a clients vinculats</string>
<string name="welcome_android_auto_detail">També podeu usar l\'EVMap des d\'Android Auto en cotxes compatibles. Simplement seleccioneu l\'aplicació EVMap al menú Android Auto.</string>
<string name="auto_chargeprice_vehicle_ambiguous">Múltiples vehicles seleccionats a l\'aplicació coincideixen amb aquest vehicle (%1$s %2$s).</string>
<string name="all_selected">Tota la selecció</string>
<string name="category_parking_multi">Aparcament de diverses plantes</string>
<string name="charging_paid">de pagament</string>
<string name="parking_paid">de pagament</string>
</resources>
+2
View File
@@ -395,4 +395,6 @@
<string name="auto_use_new_map_screen">Nová obrazovka mapy (beta)</string>
<string name="auto_confirm_delete_profile">Odstranit profil filtru „%s“?</string>
<string name="no_share_app_found">Nepodařilo se najít aplikaci ke sdílení</string>
<string name="system_setting_not_supported">Váš systém nepodporuje toto nastavení</string>
<string name="plug_mcs">MCS</string>
</resources>
+6 -4
View File
@@ -7,6 +7,7 @@
<string name="no_browser_app_found">Bitte installiere einen Webbrowser</string>
<string name="no_email_app_found">Bitte installiere eine E-Mail-App</string>
<string name="no_share_app_found">Keine App zum Teilen gefunden</string>
<string name="system_setting_not_supported">Diese Einstellung wird von deinem System nicht unterstützt</string>
<string name="address">Adresse</string>
<string name="operator">Betreiber</string>
<string name="network">Verbund</string>
@@ -92,7 +93,7 @@
<string name="menu_manage_filter_profiles">Filterprofile verwalten</string>
<string name="go_to_chargeprice">Preisvergleich</string>
<string name="fault_report">Störungsmeldung</string>
<string name="fault_report_date">Störungsmeldung (Letztes Update: %s)</string>
<string name="fault_report_date">Störungsmeldung (letzte Aktualisierung: %s)</string>
<string name="filter_networks">Verbünde</string>
<string name="filter_operators">Betreiber</string>
<string name="filter_chargecards">Ladetarife</string>
@@ -260,7 +261,7 @@
<string name="help">Hilfe</string>
<string name="settings_android_auto">Android Auto</string>
<string name="pref_chargeprice_allow_unbalanced_load">Schieflast erlauben</string>
<string name="pref_chargeprice_allow_unbalanced_load_summary">Einphasiges Laden mit mehr als 4.5 kW erlauben</string>
<string name="pref_chargeprice_allow_unbalanced_load_summary">Einphasiges Laden mit mehr als 4,5 kW erlauben</string>
<string name="pref_map_rotate_gestures_enabled">Kartenrotation</string>
<string name="pref_map_rotate_gestures_on">Karte mit zwei Fingern rotieren</string>
<string name="pref_map_rotate_gestures_off">Karte immer nach Norden ausrichten</string>
@@ -300,7 +301,7 @@
<string name="compass">Kompass</string>
<string name="charger_website">Website</string>
<string name="location_status">Standortdienste-Status</string>
<string name="pref_tesla_account">Tesla-Account</string>
<string name="pref_tesla_account">Tesla-Konto</string>
<string name="pref_tesla_account_enabled">Angemeldet als %s</string>
<string name="pref_tesla_account_disabled">Anmelden, um Echtzeitdaten für Tesla Supercharger zu sehen. Kein Tesla-Fahrzeug notwendig</string>
<string name="logging_in">Anmelden…</string>
@@ -309,7 +310,7 @@
<string name="login">Anmelden</string>
<string name="login_error">Anmeldung fehlgeschlagen</string>
<string name="tesla_pricing_owners">Nur Tesla-Fahrzeuge:</string>
<string name="tesla_pricing_members">Tesla-Fahrzeuge &amp; Mitglieder:</string>
<string name="tesla_pricing_members">Tesla-Fahrzeuge &amp; -Mitglieder:</string>
<string name="tesla_pricing_others">Andere Kunden:</string>
<string name="pricing_up_to">bis zu %s</string>
<string name="tesla_pricing_other_times">Andere Zeiten:</string>
@@ -391,4 +392,5 @@
<string name="chargeprice_removal_2025_dialog_detail">Die Kosten für den Zugriff auf Chargeprice-Daten sind stark gestiegen und können nicht mehr durch Spenden gedeckt werden. Daher kann EVMap diese Daten nicht mehr direkt anzeigen. Hier öffnet sich nun die Chargeprice-Website. Eine alternative Lösung ist in Arbeit, wird aber Zeit brauchen und anfangs nur eingeschränkt funktionieren. Danke für eure Geduld und Unterstützung!</string>
<string name="auto_use_new_map_screen">Neue Kartendarstellung (beta)</string>
<string name="auto_confirm_delete_profile">Filterprofil „%s“ löschen?</string>
<string name="plug_mcs">MCS</string>
</resources>
+2
View File
@@ -391,4 +391,6 @@
<string name="auto_use_new_map_screen">Uus kaardivaade (beetaversioon)</string>
<string name="auto_confirm_delete_profile">Kas soovid kustutada filtriprofiili „%s“?</string>
<string name="no_share_app_found">Ei leidunud rakendust, millega sisu jagada</string>
<string name="system_setting_not_supported">Sinu nutiseade ei toeta selle seadistuse kasutamist</string>
<string name="plug_mcs">MCS</string>
</resources>
+5
View File
@@ -390,4 +390,9 @@
<string name="accessibility_employees">Impiegati</string>
<string name="accessibility_by_appointment">Per appuntamento</string>
<string name="accessibility_residents">Residenti</string>
<string name="no_share_app_found">Non è stato possibile trovare un\'app con cui condividere</string>
<string name="chargeprice_removal_2025_dialog_title">Spiacente!</string>
<string name="chargeprice_removal_2025_dialog_detail">I costi per l\'accesso ai dati di Chargeprice sono aumentati notevolmente e non possono essere coperti dalle donazioni, pertanto EVMap non è più in grado di visualizzare direttamente questi dati. Per il momento, viene reindirizzato al sito web di Chargeprice. Stiamo lavorando a una soluzione alternativa, ma ci vorrà del tempo e inizialmente potrebbe essere disponibile solo con funzionalità limitate. Grazie per la pazienza e il sostegno!</string>
<string name="auto_use_new_map_screen">Nuova schermata della mappa (beta)</string>
<string name="auto_confirm_delete_profile">Eliminare il profilo di filtro “%s“?</string>
</resources>
+2
View File
@@ -391,4 +391,6 @@
<string name="welcome_2_title">Full koll på hastigheten</string>
<string name="auto_confirm_delete_profile">Ta bort filterprofilen \"%s\"?</string>
<string name="no_share_app_found">Kunde inte hitta en app att dela med</string>
<string name="system_setting_not_supported">Din enhet stöder inte denna inställning</string>
<string name="plug_mcs">MCS</string>
</resources>
+2 -2
View File
@@ -4,8 +4,8 @@
<string name="github_link">https://github.com/ev-map/EVMap</string>
<string name="twitter_handle">\@ev_map</string>
<string name="twitter_url">https://twitter.com/ev_map</string>
<string name="mastodon_handle">\@evmap\@electroverse.tech</string>
<string name="mastodon_url">https://electroverse.tech/@evmap</string>
<string name="mastodon_handle">\@evmap\@mastodon.social</string>
<string name="mastodon_url">https://mastodon.social/@evmap</string>
<string name="goingelectric_forum_url"><![CDATA[https://www.goingelectric.de/forum/viewtopic.php?f=5&t=56342]]></string>
<string name="tff_forum_url"><![CDATA[https://tff-forum.de/t/283834]]></string>
<string name="github_sponsors_link">https://github.com/sponsors/johan12345/</string>
+2
View File
@@ -7,6 +7,7 @@
<string name="no_browser_app_found">Install a web browser first</string>
<string name="no_email_app_found">Install an email app first</string>
<string name="no_share_app_found">Could not find an app to share with</string>
<string name="system_setting_not_supported">Your system does not support this setting</string>
<string name="address">Address</string>
<string name="operator">Operator</string>
<string name="network">Network</string>
@@ -71,6 +72,7 @@
<string name="plug_cee_blau">CEE Blue</string>
<string name="plug_cee_rot">CEE Red</string>
<string name="plug_roadster_hpc">Tesla Roadster (2008) HPC</string>
<string name="plug_mcs">MCS</string>
<string name="all">all</string>
<string name="none">none</string>
<string name="show_more">more…</string>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates src="@raw/isrg_root_x1" />
<certificates src="@raw/isrg_root_x2" />
</trust-anchors>
</base-config>
</network-security-config>
+8 -18
View File
@@ -1,23 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
val kotlinVersion by extra("2.0.21")
val aboutLibsVersion by extra("12.2.4")
val navVersion by extra("2.9.3")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath("com.android.tools.build:gradle:8.12.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$aboutLibsVersion")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.test.logger) apply false
alias(libs.plugins.moshix) apply false
alias(libs.plugins.navigation.safeargs) apply false
alias(libs.plugins.aboutlibraries) apply false
}
allprojects {
@@ -0,0 +1,18 @@
Amb l'EVMap podeu trobar carregadors de vehicles elèctrics còmodament amb el vostre telèfon Android. Proporcioneu accés mòbil a les bases de dades impulsades per la comunitat de GoingElectric.de i Open Charge Map, que contenen informació sobre les ubicacions de recàrrega a tot el món. Per a molts punts de recàrrega a Europa, es pot veure informació d'estat en temps real.
Característiques:
- Disseny de material
- Mostra totes les estacions de recàrrega dels directoris GoingElectric.de i Open Charge Map
- Informació sobre disponibilitat en temps real (només a Europa)
- Comparació integrada de preus usant Chargeprice.app (només a Europa)
- Dades de mapa d'OpenStreetMap
- Cerca de llocs
- Opcions avançades de filtratge, inclosos perfils de filtratge desats
- Llista de preferits, també amb informació de disponibilitat
- Sense anuncis, de codi obert
L'EVMap és un projecte de codi obert i es pot trobar a https://github.com/ev-map/EVMap.
Aquesta aplicació no és un producte oficial de GoingElectric.de ni d'Open Charge Map, només usa les seves API públiques.
Una llista dels permisos necessaris amb explicacions està disponible aquí: https://ev-map.app/faq/:permissions
@@ -0,0 +1 @@
Trobeu estacions de recàrrega per a vehicles elèctrics
+1
View File
@@ -0,0 +1 @@
EVMap - carregadors per a VE
@@ -0,0 +1,3 @@
Fehler behoben:
- Android Auto: Scrollposition in der Liste nach Aktualisierung beibehalten
- Tesla Login funktionierte nicht
@@ -0,0 +1,9 @@
Verbesserungen:
- Unterstützung für Vulkan und OpenGL ES 3.0 zum Rendern von OpenStreetMap-Karten
- Einige Verbesserungen "unter der Haube"
- Nobil: Unterstützung für Megawatt Charging System-Anschlüsse
Fehler behoben:
- Bildschirmränder wurden unter Android <= 10 nicht korrekt berücksichtigt
- Tesla-Echtzeitstatus funktionierte nicht mehr, wenn erneutes Login erforderlich war
- Abstürze behoben
@@ -0,0 +1,3 @@
Bugfixes:
- Android Auto: Keep scroll position after refresh
- Tesla login was not working
@@ -0,0 +1,9 @@
Improvements:
- Support for Vulkan and OpenGL ES 3.0 for rendering OpenStreetMap maps
- Some improvements "under the hood"
- Nobil: Support for Megawatt Charging System connectors
Bugfixes:
- Screen insets were not correctly handled on Android <= 10
- Tesla real-time status no longer worked when re-login was required
- Crashes fixed
+1
View File
@@ -16,3 +16,4 @@ org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options\="-Xmx4096M"
android.useAndroidX=true
android.nonTransitiveRClass=true
android.nonFinalResIds=true
org.gradle.configuration-cache=true
+131
View File
@@ -0,0 +1,131 @@
[versions]
agp = "9.2.1"
kotlin = "2.3.21"
ksp = "2.3.8"
testLogger = "4.0.0"
moshix = "0.35.0"
aboutlibraries = "14.2.1"
navigation = "2.9.8"
appcompat = "1.7.1"
coreKtx = "1.18.0"
coreSplashscreen = "1.2.0"
activityKtx = "1.13.0"
fragmentKtx = "1.8.9"
preferenceKtx = "1.2.1"
material = "1.14.0"
constraintlayout = "2.2.1"
recyclerview = "1.4.0"
browser = "1.9.0"
swipeRefreshLayout = "1.1.0"
viewpager2 = "1.1.0"
securityCrypto = "1.1.0"
workRuntime = "2.10.5"
retrofit = "3.0.0"
okhttp = "4.12.0"
moshi = "1.15.2"
coil = "2.7.0"
lottie = "6.6.10"
bimap = "1.1.0"
mapscaleview = "1.6.0"
carApp = "1.8.0-rc01"
anymaps = "596b491875"
playServicesMaps = "20.0.0"
places = "4.4.1" # last version that supports minSdkVersion 23
kotlinxCoroutinesPlayServices = "1.10.2"
mapLibre = "13.6.0"
mapboxServices = "5.8.0"
lifecycle = "2.9.2"
room = "2.7.2"
spatiaRoom = "1.0.1"
androidSpatialite = "2.3.0-alpha"
billing = "9.1.0"
acra = "5.13.1"
timber = "5.0.1"
leakcanary = "2.14"
junit4 = "4.13.2"
robolectric = "4.16"
androidxTestCore = "1.7.0"
androidxArchCoreTesting = "2.2.0"
androidxTestExtJunit = "1.3.0"
espressoCore = "3.7.0"
desugarJdkLibs = "2.1.5"
[libraries]
kotlin-stdlib-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
androidx-cardview = { module = "androidx.cardview:cardview", version = "1.0.0" }
androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
material = { module = "com.google.android.material:material", version.ref = "material" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
androidx-browser = { module = "androidx.browser:browser", version.ref = "browser" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swipeRefreshLayout" }
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntime" }
custom-bottom-sheet-behavior = { module = "com.github.ev-map:CustomBottomSheetBehavior", version = "e48f73ea7b" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-urlconnection = { module = "com.squareup.okhttp3:okhttp-urlconnection", version.ref = "okhttp" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
moshi-adapters = { module = "com.squareup.moshi:moshi-adapters", version.ref = "moshi" }
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
stfalcon-image-viewer = { module = "com.github.ev-map:StfalconImageViewer", version = "c6ba219517" }
aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" }
aboutlibraries = { module = "com.mikepenz:aboutlibraries", version.ref = "aboutlibraries" }
lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
bimap = { module = "io.michaelrocks.bimap:bimap", version.ref = "bimap" }
mapscaleview = { module = "com.github.pengrad:mapscaleview", version.ref = "mapscaleview" }
page-indicator-view = { module = "com.github.romandanylyk:PageIndicatorView", version = "b1bad589b5" }
locale-config-x = { module = "com.github.ev-map:locale-config-x", version = "58b036abf4" }
androidx-car-app = { module = "androidx.car.app:app", version.ref = "carApp" }
androidx-car-app-projected = { module = "androidx.car.app:app-projected", version.ref = "carApp" }
androidx-car-app-automotive = { module = "androidx.car.app:app-automotive", version.ref = "carApp" }
androidx-car-app-testing = { module = "androidx.car.app:app-testing", version.ref = "carApp" }
anymaps-base = { module = "com.github.ev-map.AnyMaps:anymaps-base", version.ref = "anymaps" }
anymaps-google = { module = "com.github.ev-map.AnyMaps:anymaps-google", version.ref = "anymaps" }
play-services-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "playServicesMaps" }
anymaps-maplibre = { module = "com.github.ev-map.AnyMaps:anymaps-maplibre", version.ref = "anymaps" }
maplibre-android-sdk-vulkan-opengl = { module = "org.maplibre.gl:android-sdk-vulkan-opengl", version.ref = "mapLibre" }
places = { module = "com.google.android.libraries.places:places", version.ref = "places" }
kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
mapbox-sdk-services = { module = "com.mapbox.mapboxsdk:mapbox-sdk-services", version.ref = "mapboxServices" }
androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
spatia-room = { module = "com.github.anboralabs:spatia-room", version.ref = "spatiaRoom" }
android-spatialite = { module = "io.github.ev-map:android-spatialite", version.ref = "androidSpatialite" }
billing = { module = "com.android.billingclient:billing", version.ref = "billing" }
billing-ktx = { module = "com.android.billingclient:billing-ktx", version.ref = "billing" }
acra-http = { module = "ch.acra:acra-http", version.ref = "acra" }
acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" }
acra-limiter = { module = "ch.acra:acra-limiter", version.ref = "acra" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
junit4 = { module = "junit:junit", version.ref = "junit4" }
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
androidx-arch-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidxArchCoreTesting" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
test-logger = { id = "com.adarshr.test-logger", version.ref = "testLogger" }
moshix = { id = "dev.zacsweers.moshix", version.ref = "moshix" }
navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "navigation" }
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
+1 -1
View File
@@ -1,6 +1,6 @@
#Sat Aug 06 15:33:46 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
+11
View File
@@ -1,2 +1,13 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name="EVMap"
include (":app")