mirror of
https://github.com/ev-map/EVMap.git
synced 2026-09-08 19:39:02 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f2635ba31 | ||
|
|
fcf5393cfa | ||
|
|
1f9da92465 | ||
|
|
986c3a2579 | ||
|
|
e9240cea0f | ||
|
|
2283d21ffa | ||
|
|
92f67d36d4 | ||
|
|
5d9b3a4a68 | ||
|
|
1f6a515480 | ||
|
|
34329e1ecc | ||
|
|
1ce1fd9256 | ||
|
|
4495c4fb92 | ||
|
|
37ede73295 | ||
|
|
6d351d4fa9 | ||
|
|
e342f3e424 | ||
|
|
305eb5fe65 | ||
|
|
10053bcc00 | ||
|
|
87cef478a0 | ||
|
|
c8907c8b1c | ||
|
|
ab5125ede9 | ||
|
|
cb3433828c | ||
|
|
3ae8f8ddf0 | ||
|
|
b9760dafd1 | ||
|
|
0043f57e2e | ||
|
|
7488dbf100 | ||
|
|
89231c99f6 | ||
|
|
6aeede295e | ||
|
|
9125ff133b | ||
|
|
0cbb2d82c0 | ||
|
|
abb00f144b | ||
|
|
e31a83377f | ||
|
|
a0e645886e | ||
|
|
b57bb5d884 | ||
|
|
b7571acc9e | ||
|
|
757c8589fb | ||
|
|
bbebc9fc20 | ||
|
|
40d894a270 | ||
|
|
88ab03e681 | ||
|
|
6b751a7371 | ||
|
|
edd451d61e | ||
|
|
15e0e6d6ae | ||
|
|
2e9b7c941b | ||
|
|
dda491c34f | ||
|
|
f6c39e1bdc | ||
|
|
f03dc0ebdb | ||
|
|
640057e0ef | ||
|
|
4fa4831fcb | ||
|
|
bb50f0bcc4 | ||
|
|
637f8dbfad | ||
|
|
fa21ffd7ff | ||
|
|
53809b55fb | ||
|
|
63a10d7f68 | ||
|
|
e117c844ed | ||
|
|
36174297e5 | ||
|
|
ac37cdde32 | ||
|
|
4345ee6550 | ||
|
|
562f2f2192 | ||
|
|
80b4ab3011 | ||
|
|
178fe804dc | ||
|
|
4a7c3f0b8f | ||
|
|
3b76e8fdfd |
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 Johan von Forstner and contributors
|
||||
Copyright (c) 2020-2026 Johan von Forstner and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 = 272
|
||||
versionName = "2.1.1"
|
||||
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.7.0"
|
||||
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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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) }
|
||||
))
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -176,8 +176,7 @@ class AvailabilityRepository(context: Context) {
|
||||
teslaOwnerAvailabilityDetector,
|
||||
TeslaGuestAvailabilityDetector(okhttp),
|
||||
NobilAvailabilityDetector(okhttp, context),
|
||||
EnBwAvailabilityDetector(okhttp),
|
||||
NewMotionAvailabilityDetector(okhttp)
|
||||
EnBwAvailabilityDetector(okhttp)
|
||||
)
|
||||
|
||||
suspend fun getAvailability(charger: ChargeLocation): Resource<ChargeLocationStatus> {
|
||||
|
||||
@@ -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()
|
||||
@@ -136,17 +137,8 @@ class EnBwAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
|
||||
var markers =
|
||||
api.getMarkers(lng - coordRange, lng + coordRange, lat - coordRange, lat + coordRange)
|
||||
|
||||
markers = markers.flatMap {
|
||||
if (it.grouped) {
|
||||
api.getMarkers(
|
||||
it.viewPort.lowerLeftLon,
|
||||
it.viewPort.upperRightLon,
|
||||
it.viewPort.lowerLeftLat,
|
||||
it.viewPort.upperRightLat
|
||||
)
|
||||
} else {
|
||||
listOf(it)
|
||||
}
|
||||
repeat(3) {
|
||||
markers = ungroupMarkers(markers)
|
||||
}
|
||||
if (markers.any { it.grouped }) throw AvailabilityDetectorException("markers still grouped")
|
||||
|
||||
@@ -241,6 +233,20 @@ class EnBwAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun ungroupMarkers(markers: List<EnBwApi.EnBwLocation>): List<EnBwApi.EnBwLocation> =
|
||||
markers.flatMap {
|
||||
if (it.grouped) {
|
||||
api.getMarkers(
|
||||
it.viewPort.lowerLeftLon,
|
||||
it.viewPort.upperRightLon,
|
||||
it.viewPort.lowerLeftLat,
|
||||
it.viewPort.upperRightLat
|
||||
)
|
||||
} else {
|
||||
listOf(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isChargerSupported(charger: ChargeLocation): Boolean {
|
||||
val country = charger.chargepriceData?.country ?: charger.address?.country
|
||||
|
||||
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
package net.vonforst.evmap.api.availability
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.ToJson
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Chargepoint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Locale
|
||||
|
||||
private const val coordRange = 0.005 // range of latitude and longitude for loading the map
|
||||
private const val maxDistance = 60 // max distance between reported positions in meters
|
||||
|
||||
interface NewMotionApi {
|
||||
@GET("markers/{lngMin}/{lngMax}/{latMin}/{latMax}/{zoom}")
|
||||
suspend fun getMarkers(
|
||||
@Path("lngMin") lngMin: Double,
|
||||
@Path("lngMax") lngMax: Double,
|
||||
@Path("latMin") latMin: Double,
|
||||
@Path("latMax") latMax: Double,
|
||||
@Path("zoom") zoom: Int = 22
|
||||
): List<NMMarker>
|
||||
|
||||
@GET("locations/{id}")
|
||||
suspend fun getLocation(@Path("id") id: Long): NMLocation
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMMarker(val coordinates: NMCoordinates, val locationUid: Long, val evseCount: Int)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMCoordinates(val latitude: Double, val longitude: Double)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMLocation(
|
||||
val uid: Long,
|
||||
val coordinates: NMCoordinates,
|
||||
val operatorName: String,
|
||||
val evses: List<NMEvse>
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMEvse(
|
||||
val evseId: String?,
|
||||
val status: String,
|
||||
val connectors: List<NMConnector>,
|
||||
val updated: ZonedDateTime?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMConnector(
|
||||
val uid: Long,
|
||||
val connectorType: String,
|
||||
val electricalProperties: NMElectricalProperties
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NMElectricalProperties(val powerType: String, val voltage: Int, val amperage: Int, val maxElectricPower: Double?) {
|
||||
fun getPower(): Double {
|
||||
maxElectricPower?.let { return it }
|
||||
val phases = when (powerType) {
|
||||
"AC1Phase" -> 1
|
||||
"AC3Phase" -> 3
|
||||
else -> 1
|
||||
}
|
||||
val volt = when (voltage) {
|
||||
277 -> 230
|
||||
else -> voltage
|
||||
}
|
||||
val power = volt * amperage * phases
|
||||
return when (power) {
|
||||
3680 -> 3.7
|
||||
11040 -> 11.0
|
||||
22080 -> 22.0
|
||||
43470 -> 43.0
|
||||
else -> power / 1000.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(client: OkHttpClient, baseUrl: String? = null): NewMotionApi {
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseUrl ?: "https://ui-map.shellrecharge.com/api/map/v2/")
|
||||
.addConverterFactory(
|
||||
MoshiConverterFactory.create(
|
||||
Moshi.Builder().add(ZonedDateTimeAdapter()).build()
|
||||
)
|
||||
)
|
||||
.client(client)
|
||||
.build()
|
||||
return retrofit.create(NewMotionApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ZonedDateTimeAdapter {
|
||||
@FromJson
|
||||
fun fromJson(value: String): ZonedDateTime? = ZonedDateTime.parse(value)
|
||||
|
||||
@ToJson
|
||||
fun toJson(value: ZonedDateTime): String = value.toString()
|
||||
}
|
||||
|
||||
data class NmStatus(
|
||||
val conn: NewMotionApi.NMConnector,
|
||||
val status: String,
|
||||
val evseId: String?,
|
||||
val updated: ZonedDateTime?
|
||||
)
|
||||
|
||||
class NewMotionAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
|
||||
BaseAvailabilityDetector(client) {
|
||||
val api = NewMotionApi.create(client, baseUrl)
|
||||
|
||||
override suspend fun getAvailability(location: ChargeLocation): ChargeLocationStatus {
|
||||
val lat = location.coordinates.lat
|
||||
val lng = location.coordinates.lng
|
||||
|
||||
// find nearest station to this position
|
||||
var markers =
|
||||
api.getMarkers(lng - coordRange, lng + coordRange, lat - coordRange, lat + coordRange)
|
||||
val nearest = markers.minByOrNull { marker ->
|
||||
distanceBetween(marker.coordinates.latitude, marker.coordinates.longitude, lat, lng)
|
||||
} ?: throw AvailabilityDetectorException("no candidates found.")
|
||||
|
||||
if (distanceBetween(
|
||||
nearest.coordinates.latitude,
|
||||
nearest.coordinates.longitude,
|
||||
lat,
|
||||
lng
|
||||
) > radius
|
||||
) {
|
||||
throw AvailabilityDetectorException("no candidates found")
|
||||
}
|
||||
|
||||
markers = if (nearest.evseCount < location.totalChargepoints) {
|
||||
// combine related stations
|
||||
markers.filter { marker ->
|
||||
distanceBetween(
|
||||
marker.coordinates.latitude,
|
||||
marker.coordinates.longitude,
|
||||
nearest.coordinates.latitude,
|
||||
nearest.coordinates.longitude
|
||||
) < maxDistance
|
||||
}
|
||||
} else {
|
||||
listOf(nearest)
|
||||
}
|
||||
|
||||
// load details
|
||||
var details = markers.map {
|
||||
api.getLocation(it.locationUid)
|
||||
}
|
||||
// only include stations from same operator
|
||||
details = details.filter {
|
||||
it.operatorName == details[0].operatorName
|
||||
}
|
||||
val connectorStatus = details.flatMap { it.evses }.flatMap { evse ->
|
||||
evse.connectors.map { connector ->
|
||||
NmStatus(connector, evse.status, evse.evseId, evse.updated)
|
||||
}
|
||||
}
|
||||
|
||||
val nmConnectors = mutableMapOf<Long, Pair<Double, String>>()
|
||||
val nmStatus = mutableMapOf<Long, ChargepointStatus>()
|
||||
val nmEvseId = mutableMapOf<Long, String>()
|
||||
val nmUpdated = mutableMapOf<Long, ZonedDateTime>()
|
||||
connectorStatus.forEach { (connector, statusStr, evseId, updated) ->
|
||||
val id = connector.uid
|
||||
val power = connector.electricalProperties.getPower()
|
||||
val type = when (connector.connectorType.lowercase(Locale.ROOT)) {
|
||||
"type3" -> Chargepoint.TYPE_3C
|
||||
"type2" -> Chargepoint.TYPE_2_UNKNOWN
|
||||
"type1" -> Chargepoint.TYPE_1
|
||||
"domestic" -> Chargepoint.SCHUKO
|
||||
"type1combo" -> Chargepoint.CCS_TYPE_1 // US CCS, aka type1_combo
|
||||
"type2combo" -> Chargepoint.CCS_TYPE_2 // EU CCS, aka type2_combo
|
||||
"tepcochademo" -> Chargepoint.CHADEMO
|
||||
"unspecified" -> "unknown"
|
||||
"unknown" -> "unknown"
|
||||
"saej1772" -> "unknown"
|
||||
else -> "unknown"
|
||||
}
|
||||
val status = when (statusStr) {
|
||||
"Unavailable" -> ChargepointStatus.FAULTED
|
||||
"Available" -> ChargepointStatus.AVAILABLE
|
||||
"Occupied" -> ChargepointStatus.CHARGING
|
||||
"Unspecified" -> ChargepointStatus.UNKNOWN
|
||||
else -> ChargepointStatus.UNKNOWN
|
||||
}
|
||||
nmConnectors.put(id, power to type)
|
||||
nmStatus.put(id, status)
|
||||
evseId?.let { nmEvseId[id] = it }
|
||||
updated?.let { nmUpdated[id] = it }
|
||||
}
|
||||
|
||||
val match = matchChargepoints(nmConnectors, location.chargepointsMerged)
|
||||
val chargepointStatus = match.mapValues { entry ->
|
||||
entry.value.map { nmStatus[it]!! }
|
||||
}
|
||||
val evseIds = if (nmEvseId.size == nmStatus.size) match.mapValues { entry ->
|
||||
entry.value.map { nmEvseId[it]!! }
|
||||
} else null
|
||||
val updated = match.mapValues { entry -> entry.value.map { nmUpdated[it]?.toInstant() } }
|
||||
return ChargeLocationStatus(
|
||||
chargepointStatus,
|
||||
"NewMotion",
|
||||
evseIds,
|
||||
lastChange = updated
|
||||
)
|
||||
}
|
||||
|
||||
override fun isChargerSupported(charger: ChargeLocation): Boolean {
|
||||
// NewMotion is our fallback
|
||||
return when (charger.dataSource) {
|
||||
"goingelectric" -> charger.network != "Tesla Supercharger"
|
||||
"nobil" -> charger.network != "Tesla"
|
||||
"openchargemap" -> charger.chargepriceData?.network !in listOf("23", "3534")
|
||||
"openstreetmap" -> charger.operator !in listOf("Tesla, Inc.", "Tesla")
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
-1
@@ -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()
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.car.app.model.Pane
|
||||
import androidx.car.app.model.Place
|
||||
import androidx.car.app.model.PlaceMarker
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.RowSection
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import net.vonforst.evmap.R
|
||||
@@ -54,7 +55,7 @@ class ChargerListFormatter(
|
||||
private val iconGen = ChargerIconGenerator(carContext, null, height = 96)
|
||||
var favorites: Set<Long> = emptySet()
|
||||
|
||||
fun buildChargerList(
|
||||
fun buildChargerListLegacy(
|
||||
chargers: List<ChargeLocation>?,
|
||||
availabilities: Map<Long, Pair<ZonedDateTime, ChargeLocationStatus?>>
|
||||
): ItemList? {
|
||||
@@ -103,6 +104,55 @@ class ChargerListFormatter(
|
||||
}
|
||||
}
|
||||
|
||||
fun buildChargerList(
|
||||
chargers: List<ChargeLocation>?,
|
||||
availabilities: Map<Long, Pair<ZonedDateTime, ChargeLocationStatus?>>
|
||||
): RowSection? {
|
||||
return if (chargers != null) {
|
||||
val chargerList = chargers.take(screen.maxRows)
|
||||
val builder = RowSection.Builder()
|
||||
// only show the city if not all chargers are in the same city
|
||||
val showCity = chargerList.map { it.address?.city }.distinct().size > 1
|
||||
chargerList.forEach { charger ->
|
||||
builder.addItem(
|
||||
formatCharger(
|
||||
charger,
|
||||
availabilities,
|
||||
showCity,
|
||||
charger.id in favorites
|
||||
)
|
||||
)
|
||||
}
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(
|
||||
if (screen.filterStatus == FILTERS_FAVORITES) {
|
||||
R.string.auto_no_favorites_found
|
||||
} else {
|
||||
R.string.auto_no_chargers_found
|
||||
}
|
||||
)
|
||||
)
|
||||
builder.setOnItemVisibilityChangedListener(screen)
|
||||
builder.build()
|
||||
} else {
|
||||
if (screen.loadingError) {
|
||||
val builder = RowSection.Builder()
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(R.string.connection_error)
|
||||
)
|
||||
builder.build()
|
||||
} else if (screen.locationError) {
|
||||
val builder = RowSection.Builder()
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(R.string.location_error)
|
||||
)
|
||||
builder.build()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCharger(
|
||||
charger: ChargeLocation,
|
||||
availabilities: Map<Long, Pair<ZonedDateTime, ChargeLocationStatus?>>,
|
||||
|
||||
@@ -58,7 +58,6 @@ import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -164,7 +163,7 @@ class LegacyMapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
setAnchor(Place.Builder(CarLocation.create(it.latitude, it.longitude)).build())
|
||||
}
|
||||
}
|
||||
formatter.buildChargerList(chargers, availabilities)?.let {
|
||||
formatter.buildChargerListLegacy(chargers, availabilities)?.let {
|
||||
setItemList(it)
|
||||
} ?: setLoading(true)
|
||||
setCurrentLocationEnabled(true)
|
||||
|
||||
@@ -21,9 +21,9 @@ import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Header
|
||||
import androidx.car.app.model.ListTemplate
|
||||
import androidx.car.app.model.MessageTemplate
|
||||
import androidx.car.app.model.PaneTemplate
|
||||
import androidx.car.app.model.SectionedItemTemplate
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.navigation.model.MapController
|
||||
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||
@@ -213,7 +213,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
}.build())
|
||||
}.build()
|
||||
} else {
|
||||
ListTemplate.Builder().apply {
|
||||
SectionedItemTemplate.Builder().apply {
|
||||
setHeader(Header.Builder().apply {
|
||||
setTitle(title)
|
||||
setStartHeaderAction(Action.APP_ICON)
|
||||
@@ -223,8 +223,10 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
chargers?.filterIsInstance<ChargeLocation>(),
|
||||
availabilities
|
||||
)?.let {
|
||||
setSingleList(it)
|
||||
addSection(it)
|
||||
} ?: setLoading(true)
|
||||
|
||||
setScrollStatePersistenceStrategy(SectionedItemTemplate.SCROLL_STATE_PRESERVE_INDEX)
|
||||
}.build()
|
||||
}
|
||||
return MapWithContentTemplate.Builder().apply {
|
||||
|
||||
@@ -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" -> "zł"
|
||||
"CHF" -> "Fr. "
|
||||
"CZK" -> "Kč"
|
||||
"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" -> "zł"
|
||||
"CHF" -> "Fr. "
|
||||
"CZK" -> "Kč"
|
||||
"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"
|
||||
|
||||
@@ -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<ChargeLocation>" />
|
||||
|
||||
<variable
|
||||
name="distance"
|
||||
type="Double" />
|
||||
|
||||
<variable
|
||||
name="availability"
|
||||
type="Resource<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="predictionData"
|
||||
type="PredictionData" />
|
||||
|
||||
<variable
|
||||
name="filteredAvailability"
|
||||
type="Resource<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="teslaPricing"
|
||||
type="net.vonforst.evmap.api.availability.tesla.Pricing" />
|
||||
|
||||
<variable
|
||||
name="chargeCards"
|
||||
type="java.util.Map<Long, ChargeCard>" />
|
||||
|
||||
<variable
|
||||
name="filteredChargeCards"
|
||||
type="java.util.Set<Long>" />
|
||||
|
||||
<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), "%s/%d", 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 && !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 == "not signed in" ? @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 && (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 && !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 && !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 && !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 && (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 && 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 && 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 && availability.message == "not signed in"}"
|
||||
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), "\u00D7 %d", 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), "%s/%d", 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() ? " · " + item.chargepoint.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context)) : "") : 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 && item.evseId != null) ? item.label + " · " + 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" />
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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), "\u00D7 %d", 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), "%s/%d", 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), "× %d", 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>
|
||||
@@ -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>
|
||||
@@ -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), "%s/%d", 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>
|
||||
@@ -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<BooleanFilterValue>" />
|
||||
</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<MultipleChoiceFilterValue>" />
|
||||
|
||||
<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<MultipleChoiceFilterValue>" />
|
||||
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -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<SliderFilterValue>" />
|
||||
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 & Mitglieder:</string>
|
||||
<string name="tesla_pricing_members">Tesla-Fahrzeuge & -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="hide_on_scroll_fab_behavior">net.vonforst.evmap.ui.HideOnScrollFabBehavior</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
<string name="donate_link" translatable="false">https://ev-map.app/donate/</string>
|
||||
<string name="copyright_summary">©2020–2024 Johan von Forstner and contributors</string>
|
||||
<string name="copyright_summary">©2020–2026 Johan von Forstner and contributors</string>
|
||||
<string name="acra_backend_url" translatable="false">https://acra.muc.vonforst.net/report</string>
|
||||
<string name="maplibre_attributionsDialogTitle">MapLibre Maps SDK for Android</string>
|
||||
</resources>
|
||||
@@ -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>
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
package net.vonforst.evmap.api.availability
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.okResponse
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.junit.Test
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
class NewMotionAvailabilityDetectorTest {
|
||||
val api: GoingElectricApi
|
||||
val webServer = MockWebServer()
|
||||
val newMotion: NewMotionAvailabilityDetector
|
||||
|
||||
init {
|
||||
webServer.start()
|
||||
|
||||
val apikey = ""
|
||||
val baseurl = webServer.url("/ge/").toString()
|
||||
api = GoingElectricApi.create(apikey, baseurl)
|
||||
newMotion = NewMotionAvailabilityDetector(
|
||||
OkHttpClient.Builder().build(),
|
||||
webServer.url("/nm/").toString()
|
||||
)
|
||||
|
||||
webServer.dispatcher = object : Dispatcher() {
|
||||
val notFoundResponse = MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
|
||||
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
val segments = request.requestUrl!!.pathSegments
|
||||
val urlHead = segments.subList(0, 2).joinToString("/")
|
||||
when (urlHead) {
|
||||
"ge/chargepoints" -> {
|
||||
val id = request.requestUrl!!.queryParameter("ge_id")
|
||||
return okResponse("/chargers/$id.json")
|
||||
}
|
||||
"nm/markers" -> {
|
||||
val urlTail = segments.subList(2, segments.size).joinToString("/")
|
||||
val id = when (urlTail) {
|
||||
"9.56608/9.576080000000001/54.5066/54.516600000000004/22" -> 2105
|
||||
"9.539283999999999/9.549284/54.471699/54.481699000000006/22" -> 18284
|
||||
else -> -1
|
||||
}
|
||||
return okResponse("/newmotion/$id/markers.json")
|
||||
}
|
||||
"nm/locations" -> {
|
||||
val id = segments.last()
|
||||
return okResponse("/newmotion/$id.json")
|
||||
}
|
||||
else -> return notFoundResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readResource(s: String) =
|
||||
NewMotionAvailabilityDetectorTest::class.java.getResource(s)?.readText()
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun apiTest() {
|
||||
for (chargepoint in listOf(2105L, 18284L)) {
|
||||
val charger = runBlocking { api.getChargepointDetail(chargepoint).body()!! }
|
||||
.chargelocations!![0].convert("", true) as ChargeLocation
|
||||
println(charger)
|
||||
|
||||
runBlocking {
|
||||
val result = newMotion.getAvailability(charger)
|
||||
println(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-18
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
EVMap - carregadors per a VE
|
||||
@@ -0,0 +1,2 @@
|
||||
Verbesserungen:
|
||||
- Verbesserungen bei Echtzeitstatus
|
||||
@@ -0,0 +1,3 @@
|
||||
Fehler behoben:
|
||||
- Android Auto: Scrollposition in der Liste nach Aktualisierung beibehalten
|
||||
- Tesla Login funktionierte nicht
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user