mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-25 08:07:46 -05:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91b3234a45 | ||
|
|
ab7cbc981b | ||
|
|
a2c1a2cf82 | ||
|
|
167ede4e62 | ||
|
|
63900996e7 | ||
|
|
c626f3d5a5 | ||
|
|
8779e65846 | ||
|
|
0c8bf84e56 | ||
|
|
90972cf933 | ||
|
|
7d9a9605fb | ||
|
|
a0bc0f2981 | ||
|
|
f3b4c8a8ff | ||
|
|
6a8220c1c2 | ||
|
|
84c28748a4 | ||
|
|
7c29b619a5 | ||
|
|
ccfdbbe826 | ||
|
|
7052ce3c3c | ||
|
|
d73ca8aa9d | ||
|
|
64703a8c28 | ||
|
|
eb54658bf4 | ||
|
|
54d1c8ba61 | ||
|
|
1c04f6211f | ||
|
|
45497f9208 | ||
|
|
140c634397 | ||
|
|
be1b3813a9 | ||
|
|
f7ed7f1e93 | ||
|
|
0df72ac4ad |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,5 +8,6 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
apikeys.xml
|
||||
/app/release/app-release.aab
|
||||
/app/**/*.aab
|
||||
/app/**/*.apk
|
||||
/_img/connectors/*.ai
|
||||
@@ -8,10 +8,11 @@ env:
|
||||
global:
|
||||
- secure: KYdFlMarsyXw+OHht1Atp+Kirbw9O09Ck14EjFuKb1eNtknurZ/tGEXuD+8xWh1W8W21kgHEG7s3rzru53t29buz+FW9f+ZmhEWXFP3OydyvXLw4BAVVOjm6xG2uHX/8MOGLJNM7cfaF25EPQ+kznHe84R29KaLH90mNRr2lPa4VnfbcnvDStiVaez/vJ72UoYSP5HICAzoF70yC3ZvvCK1hZv71UIysCbFE2IkxvMhG9OOGebdnRmFssaRCrvfRLjitobcLzkPWzZZIqdjNASf8/iAxX8VgGBYfVj8ID06AfMrtgXNJRCvcD0LICraQ+WPUbikMunRieGO8PNHSB5vKdPoC50aLUa0RoRb4G3QM1pR2A8xAFlIJFX2R7iY+2t24L9hRFqB98+QoQzutfkAI1T0rzem/wtpZpuan+bDawDJEHGCeYbE0aPDAl6lytgrEE9fRgV3c1jJLQzu0xIWG8YLl3iMg0hL+c0wCKXoeqrfCFS6kYmmG7W+rQp4tCZifvRbWfAXwIPQieffKxqdEuUwiUsYxdzCu9v9uU3nflEOLLuRgeMP3gV8mpur9b5GztpkfgfzcAqsF+NiY01kYgGtrgCYlMy0TxASE+UuALrtkQtU01wwhs9RH7Az0Ib3C+MT5DTjxHQCYETIViocmNEG2vfAbgHazCpGAhcY=
|
||||
- secure: Hoko50vP+Mwm/O4CWvPvjMxd1gGhi+Bultjyy1WpludSmZFCfKz45Mj1EqzeYk6MeMLvGOEkSLB5wjdXdgJ9j5gEOF5K34k4vESJA7+DqDO3I7Xw9cnWgOXdFqB0qGHar0TVP3Dfg7ZcRgtmeX6t2uoELFLiS9GvTnbZXk4PCUybd979Xi8XHjEQV7+3EZbSjtsI4GFeIK1rrjd0I+UM88zYrWnz1KhdCjWvQb4iZjo+ib6NmGGEMqR7jJPRZz3KB01Y+n5h21qq9/Nv31zQIqt2B5nRxy3vBvvqKapgprIk+hVOpNnBU8w89uWUU6tYUeFk0t7z1TYWjgaBrMmGCM+aKkQ2q5F/ygNzDwB+KkpJx709Yhf0ZX8g2yvkdz+Ok7moYuvmrOPOf4E/U3BlfZxbGtRD2bGYbDgHLFYlTn6v5J2kJHDJAz31yvF5jvJejDaPp2IBVfoMRy7ZJFmGUNHGd9Se6bRwxS++AoobP5WDrBiUXNe2KKDMs3e7vbaO+hLbZ9XHpjeWIJGUfvtTee8EHZqF/8A3ju53V4/R0ehlOv2UZbpYNqcmwrsy9/R4pgMfDkG3Q054LmYrxD8DIC9b8excVMwWRP5aQ1TqZnxO2B1/vJU87RcnGl3jekeHTdHXbRq9BMV4dAdPfB9X3nGIi2GgV0iTBk+24xccOc0=
|
||||
- secure: RsN/2nBVv0byMzwchuAhDti1AWKECg3Uzqk6Ahgaqg02zx8GHj60j0qNax7KuMUg70X3G4b3m8ZzndAU0wcJ98UyIku7ofUYgXHm0XYKTJwiyyhrKJ8pZ4qoeCoRkitIGIitlb84fSufVamcoLyLNbLUsO5OzL+Uscyhq/BwAhyOhj5FB9DM+GE9ntanQ4m3k4guMyMctR9CGS6Tk4LKJ1WCm0AnjTPalc+we+7yORY2J/9d6VHLKfaFXbpuWmrdnFfDZqxqcUODsxPVE0LuUtXyKQDTjjfQc8106Z/z19AJS+oLm/2UND84PD3MqsjX6EX0/9k3fY0OsoCuQAivDRmtevQ0bDQrTAyeBLcfnPCw/MYiJWyBcaYBAYK42EAfsFTDBRIduFB/Dpvg+8GuLZSdm4xVYpTlQ2pMtlGNWIGIRQsjk9LZ8swq8QBMiiF/bpMGKdfmnyQj8jkEOCWaAzkR9O+4E4Y7PuBENef9XuV0hLMryCrML2YXigxAKkEUOPhfnG+AbEY+g2kAMp+2EtXaG3tsGxoMhEYA+uRd3o2cacQMMwRpnePH5DYg6F05mrvdHPpt+P9UR1iHaHmjBPAYeksmrdP8bS088zcnePgiL6+6N0m3+l8Krihmxg5RyWjnH18IwX4RO+xg4x3cW+zaScCDbbotDMEYtChF+Hs=
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_53968681344a_key -iv $encrypted_53968681344a_iv -in _ci/keystore.jks.enc -out _ci/keystore.jks -d
|
||||
script:
|
||||
- "./gradlew lintDebug testDebugUnitTest"
|
||||
- "./gradlew lintFossDebug testFossDebugUnitTest lintGoogleDebug testGoogleDebugUnitTest"
|
||||
- "./gradlew assembleRelease"
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
@@ -25,7 +26,9 @@ deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: B+V5Fz8k9HbpecyMjpJuLr8aVBrdwtDBDkQh4YQ8nu+Da4AiYwEJZseWXhOWs+oms0gNen9bBxsakQQKu7GKYDs8gIXZZtANWsc0gse8xo+cYT7NqEM3jP6mM3ytAv7VNRX3N2cdL7xazELK3/5+mghfORAAdXXYKUFGG5eTKoML8zgdPVN8E9QFqiusLXqoKhxOMCSE4NS+Di7CGlUmnidRTWg6yxhE085zljmYv2owS0NRbr5a4/zW6Z9xZPALGAqsOvIvpZHuOC2s0eMJWMmYGkK/Ws/LAVxfj4U+YkFp9hlZC0zEg/JoS19Gf57QmEu+vsoQ3uOBYBFv9NPI/R9kVH6o0hcOxId3J0u+ewSGWuceGLRpizXuMxKIvLTS5j6GWkxdSieWjwh/OuVB+ciAHNM31B7GP4FWnfz0ZaEVxI/tPenNipZdl9oXdyyBQQ00vPlYp0jT80XhaMh5rDwWMUPaEjRafvymcNyqZ0iVOr0rq1CbdT92STMSmA1U3/rmhtCMD5IGD0b+gQl+VpPKe1QXViYftVxCGL+s4ke4DUZD7HR20fGs8zu61Elnwci1HufbetKFL5TmxoKSLkWFSkzrtBaJnEruZIxhNUMkUL2UPynaOcPNzLoumjHXrUb3m3s0yE4OFelmJ6mJfXswP38sS8kj3wB7R/gC4rw=
|
||||
file: app/build/outputs/apk/release/app-release.apk
|
||||
file:
|
||||
- app/build/outputs/apk/foss/release/app-foss-release.apk
|
||||
- app/build/outputs/apk/google/release/app-google-release.apk
|
||||
on:
|
||||
repo: johan12345/EVMap
|
||||
tags: true
|
||||
|
||||
6
_img/paypal.svg
Normal file
6
_img/paypal.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px"
|
||||
height="24px">
|
||||
<path
|
||||
d="M20.055,7.713c-0.677-0.842-1.673-1.41-2.764-1.615C16.773,3.971,14.877,3,13.045,3H7.057C6.425,3,5.878,3.409,5.689,4.009 c-0.015,0.04-0.026,0.082-0.036,0.125L3.034,16.262c-0.009,0.041-0.015,0.083-0.019,0.125C3.006,16.449,3,16.513,3,16.56 C3,17.354,3.648,18,4.444,18h2.316l-0.267,1.262c-0.008,0.04-0.014,0.081-0.018,0.121c-0.009,0.063-0.016,0.126-0.016,0.173 C6.461,20.353,7.109,21,7.905,21h3.259c0.056,0,0.111-0.005,0.166-0.015c0.549-0.063,1.011-0.437,1.191-0.963 c0.021-0.05,0.038-0.103,0.05-0.156L13.475,16h1.398c3.365,0,5.38-1.445,5.989-4.295C21.278,9.752,20.653,8.456,20.055,7.713z M5.137,16L7.512,5h5.533c0.293,0,1.5,0.061,2.078,1.013h-4.706c-0.626,0-1.17,0.401-1.363,0.99c-0.019,0.049-0.033,0.1-0.043,0.151 l-1.034,5.093L7.183,16H5.137z M18.906,11.287C18.5,13.188,17.293,14,14.873,14h-1.857c-0.823,0-1.338,0.652-1.405,1.198L10.721,19 H8.594l1.271-6h1.444c4.259,0,5.665-2.394,6.094-4.402c0.027-0.128,0.045-0.256,0.057-0.382c0.378,0.151,0.749,0.393,1.038,0.751 C18.971,9.557,19.108,10.337,18.906,11.287z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -13,14 +13,23 @@ android {
|
||||
applicationId "net.vonforst.evmap"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 20
|
||||
versionName "0.2.2"
|
||||
versionCode 22
|
||||
versionName "0.3.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
release {
|
||||
def isRunningOnTravis = System.getenv("CI") == "true"
|
||||
if (isRunningOnTravis) {
|
||||
// configure keystore
|
||||
storeFile = file("../_ci/keystore.jks")
|
||||
storePassword = System.getenv("keystore_password")
|
||||
keyAlias = System.getenv("keystore_alias")
|
||||
keyPassword = System.getenv("keystore_alias_password")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -35,13 +44,16 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def isRunningOnTravis = System.getenv("CI") == "true"
|
||||
if (isRunningOnTravis) {
|
||||
// configure keystore
|
||||
signingConfigs.release.storeFile = file("../_ci/keystore.jks")
|
||||
signingConfigs.release.storePassword = System.getenv("keystore_password")
|
||||
signingConfigs.release.keyAlias = System.getenv("keystore_alias")
|
||||
signingConfigs.release.keyPassword = System.getenv("keystore_alias_password")
|
||||
flavorDimensions "dependencies"
|
||||
productFlavors {
|
||||
foss {
|
||||
dimension "dependencies"
|
||||
versionNameSuffix "-foss"
|
||||
}
|
||||
google {
|
||||
dimension "dependencies"
|
||||
versionNameSuffix "-google"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -69,21 +81,25 @@ android {
|
||||
if (googleMapsKey != null) {
|
||||
variant.resValue "string", "google_maps_key", googleMapsKey
|
||||
}
|
||||
def mapboxKey = env.MAPBOX_API_KEY
|
||||
if (mapboxKey != null) {
|
||||
variant.resValue "string", "mapbox_key", mapboxKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.core:core:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.2.0-beta01'
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
@@ -97,21 +113,30 @@ dependencies {
|
||||
implementation "com.mikepenz:aboutlibraries:$about_libs_version"
|
||||
implementation 'com.airbnb.android:lottie:3.4.0'
|
||||
implementation 'io.michaelrocks:bimap:1.0.2'
|
||||
implementation 'com.mapzen.android:lost:3.0.2'
|
||||
|
||||
// AnyMaps
|
||||
def anyMapsVersion = 'e6e014dd11'
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
|
||||
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-mapbox:$anyMapsVersion"
|
||||
|
||||
// Google Maps v3 Beta
|
||||
implementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
|
||||
implementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
|
||||
implementation 'com.google.maps.android:android-maps-utils-v3:1.3.3'
|
||||
implementation 'com.android.volley:volley:1.1.1'
|
||||
implementation 'com.google.android.gms:play-services-base:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-basement:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-tasks:17.1.0'
|
||||
implementation 'com.google.auto.value:auto-value-annotations:1.6.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.android.datatransport:transport-runtime:2.2.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
googleImplementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
|
||||
googleImplementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
|
||||
googleImplementation 'com.android.volley:volley:1.1.1'
|
||||
googleImplementation 'com.google.android.gms:play-services-base:17.3.0'
|
||||
googleImplementation 'com.google.android.gms:play-services-basement:17.3.0'
|
||||
googleImplementation 'com.google.android.gms:play-services-gcm:17.0.0'
|
||||
googleImplementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
googleImplementation 'com.google.android.gms:play-services-tasks:17.1.0'
|
||||
googleImplementation 'com.google.auto.value:auto-value-annotations:1.6.3'
|
||||
googleImplementation 'com.google.code.gson:gson:2.8.6'
|
||||
googleImplementation 'com.google.android.datatransport:transport-runtime:2.2.3'
|
||||
googleImplementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
|
||||
// Mapbox places (autocomplete)
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-places-v9:0.12.0'
|
||||
|
||||
// navigation library
|
||||
def nav_version = "2.3.0"
|
||||
@@ -131,8 +156,8 @@ dependencies {
|
||||
|
||||
// billing library
|
||||
def billing_version = "3.0.0"
|
||||
implementation "com.android.billingclient:billing:$billing_version"
|
||||
implementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
googleImplementation "com.android.billingclient:billing:$billing_version"
|
||||
googleImplementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
|
||||
// debug tools
|
||||
implementation 'com.facebook.stetho:stetho:1.5.1'
|
||||
|
||||
12
app/src/foss/java/net/vonforst/evmap/Inits.kt
Normal file
12
app/src/foss/java/net/vonforst/evmap/Inits.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
|
||||
fun init(context: Context) {
|
||||
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import com.mapbox.geojson.BoundingBox
|
||||
import com.mapbox.geojson.Point
|
||||
import com.mapbox.mapboxsdk.plugins.places.autocomplete.PlaceAutocomplete
|
||||
import com.mapbox.mapboxsdk.plugins.places.autocomplete.model.PlaceOptions
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.fragment.REQUEST_AUTOCOMPLETE
|
||||
import net.vonforst.evmap.viewmodel.PlaceWithBounds
|
||||
|
||||
|
||||
fun launchAutocomplete(fragment: Fragment) {
|
||||
val placeOptions = PlaceOptions.builder()
|
||||
.build(PlaceOptions.MODE_CARDS)
|
||||
|
||||
val intent = PlaceAutocomplete.IntentBuilder()
|
||||
.accessToken(fragment.getString(R.string.mapbox_key))
|
||||
.placeOptions(placeOptions)
|
||||
.build(fragment.requireActivity())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
fragment.startActivityForResult(intent, REQUEST_AUTOCOMPLETE)
|
||||
|
||||
// show keyboard
|
||||
val imm = fragment.requireContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.toggleSoftInput(0, 0)
|
||||
}
|
||||
|
||||
fun handleAutocompleteResult(intent: Intent): PlaceWithBounds? {
|
||||
val place = PlaceAutocomplete.getPlace(intent) ?: return null
|
||||
val bbox = place.bbox()?.toLatLngBounds()
|
||||
val center = place.center()!!.toLatLng()
|
||||
return PlaceWithBounds(center, bbox)
|
||||
}
|
||||
|
||||
private fun BoundingBox.toLatLngBounds(): LatLngBounds {
|
||||
return LatLngBounds(
|
||||
southwest().toLatLng(),
|
||||
northeast().toLatLng()
|
||||
)
|
||||
}
|
||||
|
||||
private fun Point.toLatLng(): LatLng = LatLng(this.latitude(), this.longitude())
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import kotlinx.android.synthetic.foss.fragment_donate.*
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
|
||||
class DonateFragment : Fragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_donate, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
(requireActivity() as AppCompatActivity).setSupportActionBar(toolbar)
|
||||
|
||||
val navController = findNavController()
|
||||
toolbar.setupWithNavController(
|
||||
navController,
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
btnDonate.setOnClickListener {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.paypal_link))
|
||||
}
|
||||
}
|
||||
}
|
||||
51
app/src/foss/res/layout/fragment_donate.xml
Normal file
51
app/src/foss/res/layout/fragment_donate.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
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"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDonate"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/donate_paypal"
|
||||
app:icon="@drawable/ic_paypal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/donations_info"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnDonate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
8
app/src/foss/res/values-de/values.xml
Normal file
8
app/src/foss/res/values-de/values.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 30% Gebühren ab.</string>
|
||||
<string name="donate_paypal">Mit PayPal spenden</string>
|
||||
</resources>
|
||||
13
app/src/foss/res/values/values.xml
Normal file
13
app/src/foss/res/values/values.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_map_provider_default" translatable="false">mapbox</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.</string>
|
||||
<string name="donate_paypal">Donate with PayPal</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
</resources>
|
||||
27
app/src/google/java/net/vonforst/evmap/Inits.kt
Normal file
27
app/src/google/java/net/vonforst/evmap/Inits.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.libraries.places.api.Places
|
||||
|
||||
fun init(context: Context) {
|
||||
Places.initialize(context, context.getString(R.string.google_maps_key));
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
val request = 9000
|
||||
val apiAvailability = GoogleApiAvailability.getInstance()
|
||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
|
||||
if (resultCode != ConnectionResult.SUCCESS) {
|
||||
if (apiAvailability.isUserResolvableError(resultCode)) {
|
||||
apiAvailability.getErrorDialog(activity, resultCode, request).show()
|
||||
} else {
|
||||
Log.d("EVMap", "This device is not supported.")
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.vonforst.evmap.adapter
|
||||
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.viewmodel.DonationItem
|
||||
|
||||
class DonationAdapter() : DataBindingAdapter<DonationItem>() {
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_donation
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.car2go.maps.google.adapter.AnyMapAdapter
|
||||
import com.google.android.libraries.places.api.model.Place
|
||||
import com.google.android.libraries.places.widget.Autocomplete
|
||||
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
|
||||
import net.vonforst.evmap.fragment.REQUEST_AUTOCOMPLETE
|
||||
import net.vonforst.evmap.viewmodel.PlaceWithBounds
|
||||
|
||||
fun launchAutocomplete(fragment: Fragment) {
|
||||
val fields = listOf(Place.Field.LAT_LNG, Place.Field.VIEWPORT)
|
||||
val intent: Intent = Autocomplete.IntentBuilder(
|
||||
AutocompleteActivityMode.OVERLAY, fields
|
||||
)
|
||||
.build(fragment.requireActivity())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
fragment.startActivityForResult(intent, REQUEST_AUTOCOMPLETE)
|
||||
|
||||
// show keyboard
|
||||
val imm = fragment.requireContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.toggleSoftInput(0, 0)
|
||||
}
|
||||
|
||||
fun handleAutocompleteResult(intent: Intent): PlaceWithBounds? {
|
||||
val place = Autocomplete.getPlaceFromIntent(intent)
|
||||
return PlaceWithBounds(AnyMapAdapter.adapt(place.latLng), AnyMapAdapter.adapt(place.viewport))
|
||||
}
|
||||
8
app/src/google/res/values-de/values.xml
Normal file
8
app/src/google/res/values-de/values.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 30% Gebühren ab.</string>
|
||||
</resources>
|
||||
13
app/src/google/res/values/values.xml
Normal file
13
app/src/google/res/values/values.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_map_provider_default" translatable="false">google</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.\n\nGoogle takes 30% off every donation.</string>
|
||||
</resources>
|
||||
@@ -2,6 +2,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.vonforst.evmap">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<application
|
||||
@@ -24,6 +25,9 @@
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="@string/google_maps_key" />
|
||||
<meta-data
|
||||
android:name="com.mapbox.ACCESS_TOKEN"
|
||||
android:value="@string/mapbox_key" />
|
||||
|
||||
<activity
|
||||
android:name=".MapsActivity"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A collection of ClusterItems that are nearby each other.
|
||||
*/
|
||||
public interface Cluster<T extends ClusterItem> {
|
||||
LatLng getPosition();
|
||||
|
||||
Collection<T> getItems();
|
||||
|
||||
int getSize();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
/**
|
||||
* ClusterItem represents a marker on the map.
|
||||
*/
|
||||
public interface ClusterItem {
|
||||
|
||||
/**
|
||||
* The position of this marker. This must always return the same value.
|
||||
*/
|
||||
@NonNull
|
||||
LatLng getPosition();
|
||||
|
||||
/**
|
||||
* The title of this marker.
|
||||
*/
|
||||
@Nullable
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* The description of this marker.
|
||||
*/
|
||||
@Nullable
|
||||
String getSnippet();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Base Algorithm class that implements lock/unlock functionality.
|
||||
*/
|
||||
public abstract class AbstractAlgorithm<T extends ClusterItem> implements Algorithm<T> {
|
||||
|
||||
private final ReadWriteLock mLock = new ReentrantReadWriteLock();
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
mLock.writeLock().lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
mLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Logic for computing clusters
|
||||
*/
|
||||
public interface Algorithm<T extends ClusterItem> {
|
||||
|
||||
/**
|
||||
* Adds an item to the algorithm
|
||||
*
|
||||
* @param item the item to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean addItem(T item);
|
||||
|
||||
/**
|
||||
* Adds a collection of items to the algorithm
|
||||
*
|
||||
* @param items the items to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean addItems(Collection<T> items);
|
||||
|
||||
void clearItems();
|
||||
|
||||
/**
|
||||
* Removes an item from the algorithm
|
||||
*
|
||||
* @param item the item to be removed
|
||||
* @return true if this algorithm contained the specified element (or equivalently, if this
|
||||
* algorithm changed as a result of the call).
|
||||
*/
|
||||
boolean removeItem(T item);
|
||||
|
||||
/**
|
||||
* Updates the provided item in the algorithm
|
||||
*
|
||||
* @param item the item to be updated
|
||||
* @return true if the item existed in the algorithm and was updated, or false if the item did
|
||||
* not exist in the algorithm and the algorithm contents remain unchanged.
|
||||
*/
|
||||
boolean updateItem(T item);
|
||||
|
||||
/**
|
||||
* Removes a collection of items from the algorithm
|
||||
*
|
||||
* @param items the items to be removed
|
||||
* @return true if this algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean removeItems(Collection<T> items);
|
||||
|
||||
Set<? extends Cluster<T>> getClusters(float zoom);
|
||||
|
||||
Collection<T> getItems();
|
||||
|
||||
void setMaxDistanceBetweenClusteredItems(int maxDistance);
|
||||
|
||||
int getMaxDistanceBetweenClusteredItems();
|
||||
|
||||
void lock();
|
||||
|
||||
void unlock();
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
import com.google.maps.android.geometry.Bounds;
|
||||
import com.google.maps.android.geometry.Point;
|
||||
import com.google.maps.android.projection.SphericalMercatorProjection;
|
||||
import com.google.maps.android.quadtree.PointQuadTree;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
|
||||
* hierarchical.
|
||||
* <p/>
|
||||
* High level algorithm:<br>
|
||||
* 1. Iterate over items in the order they were added (candidate clusters).<br>
|
||||
* 2. Create a cluster with the center of the item. <br>
|
||||
* 3. Add all items that are within a certain distance to the cluster. <br>
|
||||
* 4. Move any items out of an existing cluster if they are closer to another cluster. <br>
|
||||
* 5. Remove those items from the list of candidate clusters.
|
||||
* <p/>
|
||||
* Clusters have the center of the first element (not the centroid of the items within it).
|
||||
*/
|
||||
public class NonHierarchicalDistanceBasedAlgorithm<T extends ClusterItem> extends AbstractAlgorithm<T> {
|
||||
private static final int DEFAULT_MAX_DISTANCE_AT_ZOOM = 100; // essentially 100 dp.
|
||||
|
||||
private int mMaxDistance = DEFAULT_MAX_DISTANCE_AT_ZOOM;
|
||||
|
||||
/**
|
||||
* Any modifications should be synchronized on mQuadTree.
|
||||
*/
|
||||
private final Collection<QuadItem<T>> mItems = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Any modifications should be synchronized on mQuadTree.
|
||||
*/
|
||||
private final PointQuadTree<QuadItem<T>> mQuadTree = new PointQuadTree<>(0, 1, 0, 1);
|
||||
|
||||
private static final SphericalMercatorProjection PROJECTION = new SphericalMercatorProjection(1);
|
||||
|
||||
/**
|
||||
* Adds an item to the algorithm
|
||||
*
|
||||
* @param item the item to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean addItem(T item) {
|
||||
boolean result;
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
synchronized (mQuadTree) {
|
||||
result = mItems.add(quadItem);
|
||||
if (result) {
|
||||
mQuadTree.add(quadItem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a collection of items to the algorithm
|
||||
*
|
||||
* @param items the items to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean addItems(Collection<T> items) {
|
||||
boolean result = false;
|
||||
for (T item : items) {
|
||||
boolean individualResult = addItem(item);
|
||||
if (individualResult) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearItems() {
|
||||
synchronized (mQuadTree) {
|
||||
mItems.clear();
|
||||
mQuadTree.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the algorithm
|
||||
*
|
||||
* @param item the item to be removed
|
||||
* @return true if this algorithm contained the specified element (or equivalently, if this
|
||||
* algorithm changed as a result of the call).
|
||||
*/
|
||||
@Override
|
||||
public boolean removeItem(T item) {
|
||||
boolean result;
|
||||
// QuadItem delegates hashcode() and equals() to its item so,
|
||||
// removing any QuadItem to that item will remove the item
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
synchronized (mQuadTree) {
|
||||
result = mItems.remove(quadItem);
|
||||
if (result) {
|
||||
mQuadTree.remove(quadItem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a collection of items from the algorithm
|
||||
*
|
||||
* @param items the items to be removed
|
||||
* @return true if this algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean removeItems(Collection<T> items) {
|
||||
boolean result = false;
|
||||
synchronized (mQuadTree) {
|
||||
for (T item : items) {
|
||||
// QuadItem delegates hashcode() and equals() to its item so,
|
||||
// removing any QuadItem to that item will remove the item
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
boolean individualResult = mItems.remove(quadItem);
|
||||
if (individualResult) {
|
||||
mQuadTree.remove(quadItem);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the provided item in the algorithm
|
||||
*
|
||||
* @param item the item to be updated
|
||||
* @return true if the item existed in the algorithm and was updated, or false if the item did
|
||||
* not exist in the algorithm and the algorithm contents remain unchanged.
|
||||
*/
|
||||
@Override
|
||||
public boolean updateItem(T item) {
|
||||
// TODO - Can this be optimized to update the item in-place if the location hasn't changed?
|
||||
boolean result;
|
||||
synchronized (mQuadTree) {
|
||||
result = removeItem(item);
|
||||
if (result) {
|
||||
// Only add the item if it was removed (to help prevent accidental duplicates on map)
|
||||
result = addItem(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<? extends Cluster<T>> getClusters(float zoom) {
|
||||
final int discreteZoom = (int) zoom;
|
||||
|
||||
final double zoomSpecificSpan = mMaxDistance / Math.pow(2, discreteZoom) / 256;
|
||||
|
||||
final Set<QuadItem<T>> visitedCandidates = new HashSet<>();
|
||||
final Set<Cluster<T>> results = new HashSet<>();
|
||||
final Map<QuadItem<T>, Double> distanceToCluster = new HashMap<>();
|
||||
final Map<QuadItem<T>, StaticCluster<T>> itemToCluster = new HashMap<>();
|
||||
|
||||
synchronized (mQuadTree) {
|
||||
for (QuadItem<T> candidate : getClusteringItems(mQuadTree, zoom)) {
|
||||
if (visitedCandidates.contains(candidate)) {
|
||||
// Candidate is already part of another cluster.
|
||||
continue;
|
||||
}
|
||||
|
||||
Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan);
|
||||
Collection<QuadItem<T>> clusterItems;
|
||||
clusterItems = mQuadTree.search(searchBounds);
|
||||
if (clusterItems.size() == 1) {
|
||||
// Only the current marker is in range. Just add the single item to the results.
|
||||
results.add(candidate);
|
||||
visitedCandidates.add(candidate);
|
||||
distanceToCluster.put(candidate, 0d);
|
||||
continue;
|
||||
}
|
||||
StaticCluster<T> cluster = new StaticCluster<>(candidate.mClusterItem.getPosition());
|
||||
results.add(cluster);
|
||||
|
||||
for (QuadItem<T> clusterItem : clusterItems) {
|
||||
Double existingDistance = distanceToCluster.get(clusterItem);
|
||||
double distance = distanceSquared(clusterItem.getPoint(), candidate.getPoint());
|
||||
if (existingDistance != null) {
|
||||
// Item already belongs to another cluster. Check if it's closer to this cluster.
|
||||
if (existingDistance < distance) {
|
||||
continue;
|
||||
}
|
||||
// Move item to the closer cluster.
|
||||
itemToCluster.get(clusterItem).remove(clusterItem.mClusterItem);
|
||||
}
|
||||
distanceToCluster.put(clusterItem, distance);
|
||||
cluster.add(clusterItem.mClusterItem);
|
||||
itemToCluster.put(clusterItem, cluster);
|
||||
}
|
||||
visitedCandidates.addAll(clusterItems);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, float zoom) {
|
||||
return mItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getItems() {
|
||||
final Set<T> items = new LinkedHashSet<>();
|
||||
synchronized (mQuadTree) {
|
||||
for (QuadItem<T> quadItem : mItems) {
|
||||
items.add(quadItem.mClusterItem);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxDistanceBetweenClusteredItems(int maxDistance) {
|
||||
mMaxDistance = maxDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxDistanceBetweenClusteredItems() {
|
||||
return mMaxDistance;
|
||||
}
|
||||
|
||||
private double distanceSquared(Point a, Point b) {
|
||||
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
|
||||
}
|
||||
|
||||
private Bounds createBoundsFromSpan(Point p, double span) {
|
||||
// TODO: Use a span that takes into account the visual size of the marker, not just its
|
||||
// LatLng.
|
||||
double halfSpan = span / 2;
|
||||
return new Bounds(
|
||||
p.x - halfSpan, p.x + halfSpan,
|
||||
p.y - halfSpan, p.y + halfSpan);
|
||||
}
|
||||
|
||||
protected static class QuadItem<T extends ClusterItem> implements PointQuadTree.Item, Cluster<T> {
|
||||
private final T mClusterItem;
|
||||
private final Point mPoint;
|
||||
private final LatLng mPosition;
|
||||
private Set<T> singletonSet;
|
||||
|
||||
private QuadItem(T item) {
|
||||
mClusterItem = item;
|
||||
mPosition = item.getPosition();
|
||||
mPoint = PROJECTION.toPoint(mPosition);
|
||||
singletonSet = Collections.singleton(mClusterItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPoint() {
|
||||
return mPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getPosition() {
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<T> getItems() {
|
||||
return singletonSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mClusterItem.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof QuadItem<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((QuadItem<?>) other).mClusterItem.equals(mClusterItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A cluster whose center is determined upon creation.
|
||||
*/
|
||||
public class StaticCluster<T extends ClusterItem> implements Cluster<T> {
|
||||
private final LatLng mCenter;
|
||||
private final List<T> mItems = new ArrayList<T>();
|
||||
|
||||
public StaticCluster(LatLng center) {
|
||||
mCenter = center;
|
||||
}
|
||||
|
||||
public boolean add(T t) {
|
||||
return mItems.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getPosition() {
|
||||
return mCenter;
|
||||
}
|
||||
|
||||
public boolean remove(T t) {
|
||||
return mItems.remove(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getItems() {
|
||||
return mItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StaticCluster{" +
|
||||
"mCenter=" + mCenter +
|
||||
", mItems.size=" + mItems.size() +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mCenter.hashCode() + mItems.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof StaticCluster<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((StaticCluster<?>) other).mCenter.equals(mCenter)
|
||||
&& ((StaticCluster<?>) other).mItems.equals(mItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.geometry;
|
||||
|
||||
/**
|
||||
* Represents an area in the cartesian plane.
|
||||
*/
|
||||
public class Bounds {
|
||||
public final double minX;
|
||||
public final double minY;
|
||||
|
||||
public final double maxX;
|
||||
public final double maxY;
|
||||
|
||||
public final double midX;
|
||||
public final double midY;
|
||||
|
||||
public Bounds(double minX, double maxX, double minY, double maxY) {
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
|
||||
midX = (minX + maxX) / 2;
|
||||
midY = (minY + maxY) / 2;
|
||||
}
|
||||
|
||||
public boolean contains(double x, double y) {
|
||||
return minX <= x && x <= maxX && minY <= y && y <= maxY;
|
||||
}
|
||||
|
||||
public boolean contains(Point point) {
|
||||
return contains(point.x, point.y);
|
||||
}
|
||||
|
||||
public boolean intersects(double minX, double maxX, double minY, double maxY) {
|
||||
return minX < this.maxX && this.minX < maxX && minY < this.maxY && this.minY < maxY;
|
||||
}
|
||||
|
||||
public boolean intersects(Bounds bounds) {
|
||||
return intersects(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY);
|
||||
}
|
||||
|
||||
public boolean contains(Bounds bounds) {
|
||||
return bounds.minX >= minX && bounds.maxX <= maxX && bounds.minY >= minY && bounds.maxY <= maxY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.geometry;
|
||||
|
||||
public class Point {
|
||||
public final double x;
|
||||
public final double y;
|
||||
|
||||
public Point(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.projection;
|
||||
|
||||
/**
|
||||
* @deprecated since 0.2. Use {@link com.google.maps.android.geometry.Point} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Point extends com.google.maps.android.geometry.Point {
|
||||
public Point(double x, double y) {
|
||||
super(x, y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.projection;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
public class SphericalMercatorProjection {
|
||||
final double mWorldWidth;
|
||||
|
||||
public SphericalMercatorProjection(final double worldWidth) {
|
||||
mWorldWidth = worldWidth;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public Point toPoint(final LatLng latLng) {
|
||||
final double x = latLng.longitude / 360 + .5;
|
||||
final double siny = Math.sin(Math.toRadians(latLng.latitude));
|
||||
final double y = 0.5 * Math.log((1 + siny) / (1 - siny)) / -(2 * Math.PI) + .5;
|
||||
|
||||
return new Point(x * mWorldWidth, y * mWorldWidth);
|
||||
}
|
||||
|
||||
public LatLng toLatLng(com.google.maps.android.geometry.Point point) {
|
||||
final double x = point.x / mWorldWidth - 0.5;
|
||||
final double lng = x * 360;
|
||||
|
||||
double y = .5 - (point.y / mWorldWidth);
|
||||
final double lat = 90 - Math.toDegrees(Math.atan(Math.exp(-y * 2 * Math.PI)) * 2);
|
||||
|
||||
return new LatLng(lat, lng);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.quadtree;
|
||||
|
||||
import com.google.maps.android.geometry.Bounds;
|
||||
import com.google.maps.android.geometry.Point;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A quad tree which tracks items with a Point geometry.
|
||||
* See http://en.wikipedia.org/wiki/Quadtree for details on the data structure.
|
||||
* This class is not thread safe.
|
||||
*/
|
||||
public class PointQuadTree<T extends PointQuadTree.Item> {
|
||||
public interface Item {
|
||||
Point getPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* The bounds of this quad.
|
||||
*/
|
||||
private final Bounds mBounds;
|
||||
|
||||
/**
|
||||
* The depth of this quad in the tree.
|
||||
*/
|
||||
private final int mDepth;
|
||||
|
||||
/**
|
||||
* Maximum number of elements to store in a quad before splitting.
|
||||
*/
|
||||
private final static int MAX_ELEMENTS = 50;
|
||||
|
||||
/**
|
||||
* The elements inside this quad, if any.
|
||||
*/
|
||||
private Set<T> mItems;
|
||||
|
||||
/**
|
||||
* Maximum depth.
|
||||
*/
|
||||
private final static int MAX_DEPTH = 40;
|
||||
|
||||
/**
|
||||
* Child quads.
|
||||
*/
|
||||
private List<PointQuadTree<T>> mChildren = null;
|
||||
|
||||
/**
|
||||
* Creates a new quad tree with specified bounds.
|
||||
*
|
||||
* @param minX
|
||||
* @param maxX
|
||||
* @param minY
|
||||
* @param maxY
|
||||
*/
|
||||
public PointQuadTree(double minX, double maxX, double minY, double maxY) {
|
||||
this(new Bounds(minX, maxX, minY, maxY));
|
||||
}
|
||||
|
||||
public PointQuadTree(Bounds bounds) {
|
||||
this(bounds, 0);
|
||||
}
|
||||
|
||||
private PointQuadTree(double minX, double maxX, double minY, double maxY, int depth) {
|
||||
this(new Bounds(minX, maxX, minY, maxY), depth);
|
||||
}
|
||||
|
||||
private PointQuadTree(Bounds bounds, int depth) {
|
||||
mBounds = bounds;
|
||||
mDepth = depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an item.
|
||||
*/
|
||||
public void add(T item) {
|
||||
Point point = item.getPoint();
|
||||
if (this.mBounds.contains(point.x, point.y)) {
|
||||
insert(point.x, point.y, item);
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(double x, double y, T item) {
|
||||
if (this.mChildren != null) {
|
||||
if (y < mBounds.midY) {
|
||||
if (x < mBounds.midX) { // top left
|
||||
mChildren.get(0).insert(x, y, item);
|
||||
} else { // top right
|
||||
mChildren.get(1).insert(x, y, item);
|
||||
}
|
||||
} else {
|
||||
if (x < mBounds.midX) { // bottom left
|
||||
mChildren.get(2).insert(x, y, item);
|
||||
} else {
|
||||
mChildren.get(3).insert(x, y, item);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mItems == null) {
|
||||
mItems = new LinkedHashSet<>();
|
||||
}
|
||||
mItems.add(item);
|
||||
if (mItems.size() > MAX_ELEMENTS && mDepth < MAX_DEPTH) {
|
||||
split();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this quad.
|
||||
*/
|
||||
private void split() {
|
||||
mChildren = new ArrayList<PointQuadTree<T>>(4);
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.minX, mBounds.midX, mBounds.minY, mBounds.midY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.midX, mBounds.maxX, mBounds.minY, mBounds.midY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.minX, mBounds.midX, mBounds.midY, mBounds.maxY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.midX, mBounds.maxX, mBounds.midY, mBounds.maxY, mDepth + 1));
|
||||
|
||||
Set<T> items = mItems;
|
||||
mItems = null;
|
||||
|
||||
for (T item : items) {
|
||||
// re-insert items into child quads.
|
||||
insert(item.getPoint().x, item.getPoint().y, item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given item from the set.
|
||||
*
|
||||
* @return whether the item was removed.
|
||||
*/
|
||||
public boolean remove(T item) {
|
||||
Point point = item.getPoint();
|
||||
if (this.mBounds.contains(point.x, point.y)) {
|
||||
return remove(point.x, point.y, item);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean remove(double x, double y, T item) {
|
||||
if (this.mChildren != null) {
|
||||
if (y < mBounds.midY) {
|
||||
if (x < mBounds.midX) { // top left
|
||||
return mChildren.get(0).remove(x, y, item);
|
||||
} else { // top right
|
||||
return mChildren.get(1).remove(x, y, item);
|
||||
}
|
||||
} else {
|
||||
if (x < mBounds.midX) { // bottom left
|
||||
return mChildren.get(2).remove(x, y, item);
|
||||
} else {
|
||||
return mChildren.get(3).remove(x, y, item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mItems == null) {
|
||||
return false;
|
||||
} else {
|
||||
return mItems.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all points from the quadTree
|
||||
*/
|
||||
public void clear() {
|
||||
mChildren = null;
|
||||
if (mItems != null) {
|
||||
mItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all items within a given bounds.
|
||||
*/
|
||||
public Collection<T> search(Bounds searchBounds) {
|
||||
final List<T> results = new ArrayList<T>();
|
||||
search(searchBounds, results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private void search(Bounds searchBounds, Collection<T> results) {
|
||||
if (!mBounds.intersects(searchBounds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mChildren != null) {
|
||||
for (PointQuadTree<T> quad : mChildren) {
|
||||
quad.search(searchBounds, results);
|
||||
}
|
||||
} else if (mItems != null) {
|
||||
if (searchBounds.contains(mBounds)) {
|
||||
results.addAll(mItems);
|
||||
} else {
|
||||
for (T item : mItems) {
|
||||
if (searchBounds.contains(item.getPoint())) {
|
||||
results.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
268
app/src/main/java/com/google/maps/android/ui/IconGenerator.java
Normal file
268
app/src/main/java/com/google/maps/android/ui/IconGenerator.java
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.vonforst.evmap.R;
|
||||
|
||||
/**
|
||||
* IconGenerator generates icons that contain text (or custom content) within an info
|
||||
* window-like shape.
|
||||
* <p/>
|
||||
* The icon {@link Bitmap}s generated by the factory should be used in conjunction with a
|
||||
* BitmapDescriptorFactory.
|
||||
* <p/>
|
||||
* This class is not thread safe.
|
||||
*/
|
||||
public class IconGenerator {
|
||||
private final Context mContext;
|
||||
|
||||
private ViewGroup mContainer;
|
||||
private RotationLayout mRotationLayout;
|
||||
private TextView mTextView;
|
||||
private View mContentView;
|
||||
|
||||
private int mRotation;
|
||||
|
||||
private float mAnchorU = 0.5f;
|
||||
private float mAnchorV = 1f;
|
||||
|
||||
/**
|
||||
* Creates a new IconGenerator with the default style.
|
||||
*/
|
||||
public IconGenerator(Context context) {
|
||||
mContext = context;
|
||||
mContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.amu_text_bubble, null);
|
||||
mRotationLayout = (RotationLayout) mContainer.getChildAt(0);
|
||||
mContentView = mTextView = (TextView) mRotationLayout.findViewById(R.id.amu_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content, then creates an icon with the current style.
|
||||
*
|
||||
* @param text the text content to display inside the icon.
|
||||
*/
|
||||
public Bitmap makeIcon(CharSequence text) {
|
||||
if (mTextView != null) {
|
||||
mTextView.setText(text);
|
||||
}
|
||||
|
||||
return makeIcon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an icon with the current content and style.
|
||||
* <p/>
|
||||
* This method is useful if a custom view has previously been set, or if text content is not
|
||||
* applicable.
|
||||
*/
|
||||
public Bitmap makeIcon() {
|
||||
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
mContainer.measure(measureSpec, measureSpec);
|
||||
|
||||
int measuredWidth = mContainer.getMeasuredWidth();
|
||||
int measuredHeight = mContainer.getMeasuredHeight();
|
||||
|
||||
mContainer.layout(0, 0, measuredWidth, measuredHeight);
|
||||
|
||||
if (mRotation == 1 || mRotation == 3) {
|
||||
measuredHeight = mContainer.getMeasuredWidth();
|
||||
measuredWidth = mContainer.getMeasuredHeight();
|
||||
}
|
||||
|
||||
Bitmap r = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
|
||||
r.eraseColor(Color.TRANSPARENT);
|
||||
|
||||
Canvas canvas = new Canvas(r);
|
||||
|
||||
switch (mRotation) {
|
||||
case 0:
|
||||
// do nothing
|
||||
break;
|
||||
case 1:
|
||||
canvas.translate(measuredWidth, 0);
|
||||
canvas.rotate(90);
|
||||
break;
|
||||
case 2:
|
||||
canvas.rotate(180, measuredWidth / 2, measuredHeight / 2);
|
||||
break;
|
||||
case 3:
|
||||
canvas.translate(0, measuredHeight);
|
||||
canvas.rotate(270);
|
||||
break;
|
||||
}
|
||||
mContainer.draw(canvas);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the child view for the icon.
|
||||
* <p/>
|
||||
* If the view contains a {@link TextView} with the id "text", operations such as {@link
|
||||
* #setTextAppearance} and {@link #makeIcon(CharSequence)} will operate upon that {@link TextView}.
|
||||
*/
|
||||
public void setContentView(View contentView) {
|
||||
mRotationLayout.removeAllViews();
|
||||
mRotationLayout.addView(contentView);
|
||||
mContentView = contentView;
|
||||
final View view = mRotationLayout.findViewById(R.id.amu_text);
|
||||
mTextView = view instanceof TextView ? (TextView) view : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the contents of the icon.
|
||||
*
|
||||
* @param degrees the amount the contents should be rotated, as a multiple of 90 degrees.
|
||||
*/
|
||||
public void setContentRotation(int degrees) {
|
||||
mRotationLayout.setViewRotation(degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the icon.
|
||||
*
|
||||
* @param degrees the amount the icon should be rotated, as a multiple of 90 degrees.
|
||||
*/
|
||||
public void setRotation(int degrees) {
|
||||
mRotation = ((degrees + 360) % 360) / 90;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return u coordinate of the anchor, with rotation applied.
|
||||
*/
|
||||
public float getAnchorU() {
|
||||
return rotateAnchor(mAnchorU, mAnchorV);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return v coordinate of the anchor, with rotation applied.
|
||||
*/
|
||||
public float getAnchorV() {
|
||||
return rotateAnchor(mAnchorV, mAnchorU);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the anchor around (u, v) = (0, 0).
|
||||
*/
|
||||
private float rotateAnchor(float u, float v) {
|
||||
switch (mRotation) {
|
||||
case 0:
|
||||
return u;
|
||||
case 1:
|
||||
return 1 - v;
|
||||
case 2:
|
||||
return 1 - u;
|
||||
case 3:
|
||||
return v;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color, size, style, hint color, and highlight color from the specified
|
||||
* <code>TextAppearance</code> resource.
|
||||
*
|
||||
* @param resid the identifier of the resource.
|
||||
*/
|
||||
public void setTextAppearance(Context context, int resid) {
|
||||
if (mTextView != null) {
|
||||
mTextView.setTextAppearance(context, resid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color, size, style, hint color, and highlight color from the specified
|
||||
* <code>TextAppearance</code> resource.
|
||||
*
|
||||
* @param resid the identifier of the resource.
|
||||
*/
|
||||
public void setTextAppearance(int resid) {
|
||||
setTextAppearance(mContext, resid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the background to a given Drawable, or remove the background.
|
||||
*
|
||||
* @param background the Drawable to use as the background, or null to remove the background.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
// View#setBackgroundDrawable is compatible with pre-API level 16 (Jelly Bean).
|
||||
public void setBackground(Drawable background) {
|
||||
mContainer.setBackgroundDrawable(background);
|
||||
|
||||
// Force setting of padding.
|
||||
// setBackgroundDrawable does not call setPadding if the background has 0 padding.
|
||||
if (background != null) {
|
||||
Rect rect = new Rect();
|
||||
background.getPadding(rect);
|
||||
mContainer.setPadding(rect.left, rect.top, rect.right, rect.bottom);
|
||||
} else {
|
||||
mContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the padding of the content view. The default padding of the content view (i.e. text
|
||||
* view) is 5dp top/bottom and 10dp left/right.
|
||||
*
|
||||
* @param left the left padding in pixels.
|
||||
* @param top the top padding in pixels.
|
||||
* @param right the right padding in pixels.
|
||||
* @param bottom the bottom padding in pixels.
|
||||
*/
|
||||
public void setContentPadding(int left, int top, int right, int bottom) {
|
||||
mContentView.setPadding(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public static final int STYLE_DEFAULT = 1;
|
||||
public static final int STYLE_WHITE = 2;
|
||||
public static final int STYLE_RED = 3;
|
||||
public static final int STYLE_BLUE = 4;
|
||||
public static final int STYLE_GREEN = 5;
|
||||
public static final int STYLE_PURPLE = 6;
|
||||
public static final int STYLE_ORANGE = 7;
|
||||
|
||||
private static int getStyleColor(int style) {
|
||||
switch (style) {
|
||||
default:
|
||||
case STYLE_DEFAULT:
|
||||
case STYLE_WHITE:
|
||||
return 0xffffffff;
|
||||
case STYLE_RED:
|
||||
return 0xffcc0000;
|
||||
case STYLE_BLUE:
|
||||
return 0xff0099cc;
|
||||
case STYLE_GREEN:
|
||||
return 0xff669900;
|
||||
case STYLE_PURPLE:
|
||||
return 0xff9933cc;
|
||||
case STYLE_ORANGE:
|
||||
return 0xffff8800;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* RotationLayout rotates the contents of the layout by multiples of 90 degrees.
|
||||
* <p/>
|
||||
* May not work with padding.
|
||||
*/
|
||||
public class RotationLayout extends FrameLayout {
|
||||
private int mRotation;
|
||||
|
||||
public RotationLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RotationLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public RotationLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (mRotation == 1 || mRotation == 3) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
} else {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param degrees the rotation, in degrees.
|
||||
*/
|
||||
public void setViewRotation(int degrees) {
|
||||
mRotation = ((degrees + 360) % 360) / 90;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
if (mRotation == 0) {
|
||||
super.dispatchDraw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRotation == 1) {
|
||||
canvas.translate(getWidth(), 0);
|
||||
canvas.rotate(90, getWidth() / 2, 0);
|
||||
canvas.translate(getHeight() / 2, getWidth() / 2);
|
||||
} else if (mRotation == 2) {
|
||||
canvas.rotate(180, getWidth() / 2, getHeight() / 2);
|
||||
} else {
|
||||
canvas.translate(0, getHeight());
|
||||
canvas.rotate(270, getWidth() / 2, 0);
|
||||
canvas.translate(getHeight() / 2, -getWidth() / 2);
|
||||
}
|
||||
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
public class SquareTextView extends AppCompatTextView {
|
||||
private int mOffsetTop = 0;
|
||||
private int mOffsetLeft = 0;
|
||||
|
||||
public SquareTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquareTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SquareTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
int dimension = Math.max(width, height);
|
||||
if (width > height) {
|
||||
mOffsetTop = width - height;
|
||||
mOffsetLeft = 0;
|
||||
} else {
|
||||
mOffsetTop = 0;
|
||||
mOffsetLeft = height - width;
|
||||
}
|
||||
setMeasuredDimension(dimension, dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.translate(mOffsetLeft / 2, mOffsetTop / 2);
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package net.vonforst.evmap
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
|
||||
@@ -11,6 +10,6 @@ class EvMapApplication : Application() {
|
||||
super.onCreate()
|
||||
updateNightMode(PreferenceDataSource(this))
|
||||
Stetho.initializeWithDefaults(this);
|
||||
Places.initialize(applicationContext, getString(R.string.google_maps_key));
|
||||
init(applicationContext)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
@@ -14,8 +13,6 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
@@ -64,7 +61,7 @@ class MapsActivity : AppCompatActivity() {
|
||||
|
||||
prefs = PreferenceDataSource(this)
|
||||
|
||||
checkPlayServices()
|
||||
checkPlayServices(this)
|
||||
}
|
||||
|
||||
fun navigateTo(charger: ChargeLocation) {
|
||||
@@ -110,19 +107,4 @@ class MapsActivity : AppCompatActivity() {
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun checkPlayServices(): Boolean {
|
||||
val request = 9000
|
||||
val apiAvailability = GoogleApiAvailability.getInstance()
|
||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(this)
|
||||
if (resultCode != ConnectionResult.SUCCESS) {
|
||||
if (apiAvailability.isUserResolvableError(resultCode)) {
|
||||
apiAvailability.getErrorDialog(this, resultCode, request).show()
|
||||
} else {
|
||||
Log.d("EVMap", "This device is not supported.")
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import net.vonforst.evmap.BR
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.viewmodel.DonationItem
|
||||
import net.vonforst.evmap.viewmodel.FavoritesViewModel
|
||||
|
||||
interface Equatable {
|
||||
@@ -89,8 +88,4 @@ class FavoritesAdapter(val vm: FavoritesViewModel) :
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_favorite
|
||||
|
||||
override fun getItemId(position: Int): Long = getItem(position).charger.id
|
||||
}
|
||||
|
||||
class DonationAdapter() : DataBindingAdapter<DonationItem>() {
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_donation
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package net.vonforst.evmap.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -16,9 +15,9 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.libraries.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.mapzen.android.lost.api.LocationServices
|
||||
import com.mapzen.android.lost.api.LostApiClient
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.FavoritesAdapter
|
||||
@@ -26,9 +25,9 @@ import net.vonforst.evmap.databinding.FragmentFavoritesBinding
|
||||
import net.vonforst.evmap.viewmodel.FavoritesViewModel
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
class FavoritesFragment : Fragment() {
|
||||
class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
|
||||
private lateinit var binding: FragmentFavoritesBinding
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private lateinit var locationClient: LostApiClient
|
||||
|
||||
private val vm: FavoritesViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
@@ -51,7 +50,8 @@ class FavoritesFragment : Fragment() {
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
locationClient = LostApiClient.Builder(requireContext())
|
||||
.addConnectionCallbacks(this).build()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
@@ -82,16 +82,23 @@ class FavoritesFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
locationClient.connect()
|
||||
}
|
||||
|
||||
override fun onConnected() {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
|
||||
if (location != null) {
|
||||
vm.location.value = LatLng(location.latitude, location.longitude)
|
||||
}
|
||||
val location = LocationServices.FusedLocationApi.getLastLocation(locationClient)
|
||||
if (location != null) {
|
||||
vm.location.value = LatLng(location.latitude, location.longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnectionSuspended() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
@@ -11,11 +10,12 @@ import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.*
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.SharedElementCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
@@ -33,16 +33,13 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.transition.TransitionInflater
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.libraries.maps.CameraUpdateFactory
|
||||
import com.google.android.libraries.maps.GoogleMap
|
||||
import com.google.android.libraries.maps.OnMapReadyCallback
|
||||
import com.google.android.libraries.maps.SupportMapFragment
|
||||
import com.google.android.libraries.maps.model.*
|
||||
import com.google.android.libraries.places.api.model.Place
|
||||
import com.google.android.libraries.places.widget.Autocomplete
|
||||
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.MapFragment
|
||||
import com.car2go.maps.OnMapReadyCallback
|
||||
import com.car2go.maps.model.BitmapDescriptor
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.Marker
|
||||
import com.car2go.maps.model.MarkerOptions
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialArcMotion
|
||||
import com.google.android.material.transition.MaterialContainerTransform
|
||||
@@ -50,6 +47,8 @@ import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
import com.mahc.custombottomsheetbehavior.MergedAppBarLayoutBehavior
|
||||
import com.mapzen.android.lost.api.LocationServices
|
||||
import com.mapzen.android.lost.api.LostApiClient
|
||||
import io.michaelrocks.bimap.HashBiMap
|
||||
import io.michaelrocks.bimap.MutableBiMap
|
||||
import kotlinx.android.synthetic.main.fragment_map.*
|
||||
@@ -60,7 +59,10 @@ import net.vonforst.evmap.adapter.GalleryAdapter
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocationCluster
|
||||
import net.vonforst.evmap.api.goingelectric.ChargepointListItem
|
||||
import net.vonforst.evmap.autocomplete.handleAutocompleteResult
|
||||
import net.vonforst.evmap.autocomplete.launchAutocomplete
|
||||
import net.vonforst.evmap.databinding.FragmentMapBinding
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.ClusterIconGenerator
|
||||
import net.vonforst.evmap.ui.MarkerAnimator
|
||||
@@ -72,7 +74,8 @@ const val ARG_CHARGER_ID = "chargerId"
|
||||
const val ARG_LAT = "lat"
|
||||
const val ARG_LON = "lon"
|
||||
|
||||
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback {
|
||||
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback,
|
||||
LostApiClient.ConnectionCallbacks {
|
||||
private lateinit var binding: FragmentMapBinding
|
||||
private val vm: MapViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
@@ -83,13 +86,15 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
})
|
||||
private val galleryVm: GalleryViewModel by activityViewModels()
|
||||
private var map: GoogleMap? = null
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private lateinit var mapFragment: MapFragment
|
||||
private var map: AnyMap? = null
|
||||
private lateinit var locationClient: LostApiClient
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
|
||||
private lateinit var detailAppBarBehavior: MergedAppBarLayoutBehavior
|
||||
private var markers: MutableBiMap<Marker, ChargeLocation> = HashBiMap()
|
||||
private var clusterMarkers: List<Marker> = emptyList()
|
||||
private var searchResultMarker: Marker? = null
|
||||
private var searchResultIcon: BitmapDescriptor? = null
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
private var previousChargepointIds: Set<Long>? = null
|
||||
|
||||
@@ -125,10 +130,37 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
mapFragment = MapFragment()
|
||||
val provider = PreferenceDataSource(requireContext()).mapProvider
|
||||
mapFragment.setPriority(
|
||||
arrayOf(
|
||||
when (provider) {
|
||||
"mapbox" -> MapFragment.MAPBOX
|
||||
"google" -> MapFragment.GOOGLE
|
||||
else -> null
|
||||
},
|
||||
MapFragment.GOOGLE,
|
||||
MapFragment.MAPBOX
|
||||
)
|
||||
)
|
||||
requireActivity().supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.map, mapFragment)
|
||||
.commit()
|
||||
|
||||
// reset map-related stuff (map provider may have changed)
|
||||
map = null
|
||||
markers.clear()
|
||||
clusterMarkers = emptyList()
|
||||
searchResultMarker = null
|
||||
searchResultIcon = null
|
||||
|
||||
|
||||
locationClient = LostApiClient.Builder(requireContext())
|
||||
.addConnectionCallbacks(this)
|
||||
.build()
|
||||
locationClient.connect()
|
||||
clusterIconGenerator = ClusterIconGenerator(requireContext())
|
||||
chargerIconGenerator = ChargerIconGenerator(requireContext())
|
||||
animator = MarkerAnimator(chargerIconGenerator)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
postponeEnterTransition()
|
||||
@@ -153,7 +185,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
|
||||
mapFragment.getMapAsync(this)
|
||||
bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(binding.bottomSheet)
|
||||
detailAppBarBehavior = MergedAppBarLayoutBehavior.from(binding.detailAppBar)
|
||||
@@ -181,13 +212,17 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
private fun setupClickListeners() {
|
||||
binding.fabLocate.setOnClickListener {
|
||||
if (!hasLocationPermission()) {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
ACCESS_FINE_LOCATION
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
arrayOf(ACCESS_FINE_LOCATION),
|
||||
REQUEST_LOCATION_PERMISSION
|
||||
)
|
||||
} else {
|
||||
enableLocation(true, true)
|
||||
enableLocation(moveTo = true, animate = true)
|
||||
}
|
||||
}
|
||||
binding.fabDirections.setOnClickListener {
|
||||
@@ -216,17 +251,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT
|
||||
}
|
||||
binding.search.setOnClickListener {
|
||||
val fields = listOf(Place.Field.LAT_LNG, Place.Field.VIEWPORT)
|
||||
val intent: Intent = Autocomplete.IntentBuilder(
|
||||
AutocompleteActivityMode.OVERLAY, fields
|
||||
)
|
||||
.build(requireContext())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||
startActivityForResult(intent, REQUEST_AUTOCOMPLETE)
|
||||
|
||||
val imm =
|
||||
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.toggleSoftInput(0, 0)
|
||||
launchAutocomplete(this)
|
||||
}
|
||||
binding.detailAppBar.toolbar.setNavigationOnClickListener {
|
||||
bottomSheetBehavior.state = STATE_COLLAPSED
|
||||
@@ -351,12 +376,21 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
if (place != null) {
|
||||
if (place.viewport != null) {
|
||||
map.animateCamera(CameraUpdateFactory.newLatLngBounds(place.viewport, 0))
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngBounds(place.viewport, 0))
|
||||
} else {
|
||||
map.animateCamera(CameraUpdateFactory.newLatLngZoom(place.latLng, 12f))
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngZoom(place.latLng, 12f))
|
||||
}
|
||||
|
||||
searchResultMarker = map.addMarker(MarkerOptions().position(place.latLng!!))
|
||||
if (searchResultIcon == null) {
|
||||
searchResultIcon =
|
||||
map.bitmapDescriptorFactory.fromResource(R.drawable.ic_map_marker)
|
||||
}
|
||||
searchResultMarker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(place.latLng)
|
||||
.icon(searchResultIcon)
|
||||
.anchor(0.5f, 1f)
|
||||
)
|
||||
}
|
||||
|
||||
updateBackPressedCallback()
|
||||
@@ -367,10 +401,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
updateBackPressedCallback()
|
||||
})
|
||||
vm.mapType.observe(viewLifecycleOwner, Observer {
|
||||
map?.mapType = it
|
||||
map?.setMapType(it)
|
||||
})
|
||||
vm.mapTrafficEnabled.observe(viewLifecycleOwner, Observer {
|
||||
map?.isTrafficEnabled = it
|
||||
map?.setTrafficEnabled(it)
|
||||
})
|
||||
|
||||
updateBackPressedCallback()
|
||||
@@ -528,11 +562,13 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
override fun onMapReady(map: AnyMap) {
|
||||
this.map = map
|
||||
map.uiSettings.isTiltGesturesEnabled = false
|
||||
map.isIndoorEnabled = false
|
||||
map.uiSettings.isIndoorLevelPickerEnabled = false
|
||||
chargerIconGenerator = ChargerIconGenerator(requireContext(), map.bitmapDescriptorFactory)
|
||||
animator = MarkerAnimator(chargerIconGenerator)
|
||||
map.uiSettings.setTiltGesturesEnabled(false)
|
||||
map.setIndoorEnabled(false)
|
||||
map.uiSettings.setIndoorLevelPickerEnabled(false)
|
||||
map.setOnCameraIdleListener {
|
||||
vm.mapPosition.value = MapPosition(
|
||||
map.projection.visibleRegion.latLngBounds, map.cameraPosition.zoom
|
||||
@@ -546,7 +582,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
in clusterMarkers -> {
|
||||
val newZoom = map.cameraPosition.zoom + 2
|
||||
map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.position, newZoom))
|
||||
map.animateCamera(
|
||||
map.cameraUpdateFactory.newLatLngZoom(
|
||||
marker.position,
|
||||
newZoom
|
||||
)
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
@@ -564,9 +605,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
map.setMapStyle(
|
||||
if (mode == Configuration.UI_MODE_NIGHT_YES) {
|
||||
MapStyleOptions.loadRawResourceStyle(context, R.raw.maps_night_mode)
|
||||
} else null
|
||||
if (mode == Configuration.UI_MODE_NIGHT_YES) AnyMap.Style.DARK else AnyMap.Style.NORMAL
|
||||
)
|
||||
|
||||
|
||||
@@ -577,12 +616,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
if (position != null) {
|
||||
val cameraUpdate =
|
||||
CameraUpdateFactory.newLatLngZoom(position.bounds.center, position.zoom)
|
||||
map.cameraUpdateFactory.newLatLngZoom(position.bounds.center, position.zoom)
|
||||
map.moveCamera(cameraUpdate)
|
||||
positionSet = true
|
||||
} else if (lat != null && lon != null) {
|
||||
// show given position
|
||||
val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(lat, lon), 16f)
|
||||
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(LatLng(lat, lon), 16f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
|
||||
// show charger detail after chargers were loaded
|
||||
@@ -603,13 +642,18 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
positionSet = true
|
||||
}
|
||||
if (hasLocationPermission()) {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
enableLocation(!positionSet, false)
|
||||
positionSet = true
|
||||
}
|
||||
if (!positionSet) {
|
||||
// center the camera on Europe
|
||||
val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(50.113388, 9.252536), 3.5f)
|
||||
val cameraUpdate =
|
||||
map.cameraUpdateFactory.newLatLngZoom(LatLng(50.113388, 9.252536), 3.5f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
}
|
||||
|
||||
@@ -618,33 +662,29 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@RequiresPermission(ACCESS_FINE_LOCATION)
|
||||
private fun enableLocation(moveTo: Boolean, animate: Boolean) {
|
||||
val map = this.map ?: return
|
||||
map.isMyLocationEnabled = true
|
||||
map.setMyLocationEnabled(true)
|
||||
vm.myLocationEnabled.value = true
|
||||
map.uiSettings.isMyLocationButtonEnabled = false
|
||||
if (moveTo) {
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
|
||||
if (location != null) {
|
||||
val latLng = LatLng(location.latitude, location.longitude)
|
||||
val camUpdate = CameraUpdateFactory.newLatLngZoom(latLng, 13f)
|
||||
if (animate) {
|
||||
map.animateCamera(camUpdate)
|
||||
} else {
|
||||
map.moveCamera(camUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
map.uiSettings.setMyLocationButtonEnabled(false)
|
||||
if (moveTo && locationClient.isConnected) {
|
||||
moveToCurrentLocation(map, animate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLocationPermission(): Boolean {
|
||||
val context = context ?: return false
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
@RequiresPermission(ACCESS_FINE_LOCATION)
|
||||
private fun moveToCurrentLocation(map: AnyMap, animate: Boolean) {
|
||||
val location = LocationServices.FusedLocationApi.getLastLocation(locationClient)
|
||||
if (location != null) {
|
||||
val latLng = LatLng(location.latitude, location.longitude)
|
||||
val camUpdate = map.cameraUpdateFactory.newLatLngZoom(latLng, 13f)
|
||||
if (animate) {
|
||||
map.animateCamera(camUpdate)
|
||||
} else {
|
||||
map.moveCamera(camUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -697,7 +737,16 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val marker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.visible(false)
|
||||
.icon(
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
tint,
|
||||
0,
|
||||
255,
|
||||
highlight,
|
||||
fault
|
||||
)
|
||||
)
|
||||
.anchor(0.5f, 1f)
|
||||
)
|
||||
animator.animateMarkerAppear(marker, tint, highlight, fault)
|
||||
markers[marker] = charger
|
||||
@@ -709,11 +758,19 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(cluster.coordinates.lat, cluster.coordinates.lng))
|
||||
.icon(BitmapDescriptorFactory.fromBitmap(clusterIconGenerator.makeIcon(cluster.clusterCount.toString())))
|
||||
.icon(
|
||||
map.bitmapDescriptorFactory.fromBitmap(
|
||||
clusterIconGenerator.makeIcon(
|
||||
cluster.clusterCount.toString()
|
||||
)
|
||||
)
|
||||
)
|
||||
.anchor(0.5f, 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
@@ -722,7 +779,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
when (requestCode) {
|
||||
REQUEST_LOCATION_PERMISSION -> {
|
||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
enableLocation(true, true)
|
||||
enableLocation(moveTo = true, animate = true)
|
||||
}
|
||||
}
|
||||
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
@@ -792,8 +849,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_AUTOCOMPLETE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
vm.searchResult.value = Autocomplete.getPlaceFromIntent(data!!)
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
vm.searchResult.value = handleAutocompleteResult(data)
|
||||
}
|
||||
}
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
@@ -829,4 +886,20 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnected() {
|
||||
val map = this.map ?: return
|
||||
if (vm.myLocationEnabled.value == true) {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
moveToCurrentLocation(map, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnectionSuspended() {
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@ package net.vonforst.evmap.storage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import net.vonforst.evmap.R
|
||||
import java.time.Instant
|
||||
|
||||
class PreferenceDataSource(context: Context) {
|
||||
class PreferenceDataSource(val context: Context) {
|
||||
val sp = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
var navigateUseMaps: Boolean
|
||||
@@ -42,4 +43,10 @@ class PreferenceDataSource(context: Context) {
|
||||
|
||||
val darkmode: String
|
||||
get() = sp.getString("darkmode", "default")!!
|
||||
|
||||
val mapProvider: String
|
||||
get() = sp.getString(
|
||||
"map_provider",
|
||||
context.getString(R.string.pref_map_provider_default)
|
||||
)!!
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.vonforst.evmap.ui;
|
||||
|
||||
import com.google.android.libraries.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.google.maps.android.clustering.ClusterItem
|
||||
import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
|
||||
@@ -11,8 +11,8 @@ import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.google.android.libraries.maps.model.BitmapDescriptor
|
||||
import com.google.android.libraries.maps.model.BitmapDescriptorFactory
|
||||
import com.car2go.maps.BitmapDescriptorFactory
|
||||
import com.car2go.maps.model.BitmapDescriptor
|
||||
import com.google.maps.android.ui.IconGenerator
|
||||
import com.google.maps.android.ui.SquareTextView
|
||||
import net.vonforst.evmap.R
|
||||
@@ -32,7 +32,7 @@ class ClusterIconGenerator(context: Context) : IconGenerator(context) {
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
id = com.google.maps.android.R.id.amu_text
|
||||
id = R.id.amu_text
|
||||
setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi)
|
||||
TextViewCompat.setTextAppearance(this, R.style.TextAppearance_AppCompat)
|
||||
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||
@@ -41,7 +41,7 @@ class ClusterIconGenerator(context: Context) : IconGenerator(context) {
|
||||
}
|
||||
|
||||
|
||||
class ChargerIconGenerator(val context: Context) {
|
||||
class ChargerIconGenerator(val context: Context, val factory: BitmapDescriptorFactory) {
|
||||
data class BitmapData(
|
||||
val tint: Int,
|
||||
val scale: Int,
|
||||
@@ -94,7 +94,7 @@ class ChargerIconGenerator(val context: Context) {
|
||||
cachedImg
|
||||
} else {
|
||||
val bitmap = generateBitmap(data)
|
||||
val bmd = BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
val bmd = factory.fromBitmap(bitmap)
|
||||
cache.put(data, bmd)
|
||||
bmd
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.view.animation.BounceInterpolator
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import com.google.android.libraries.maps.model.Marker
|
||||
import com.car2go.maps.model.Marker
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import kotlin.math.max
|
||||
@@ -22,7 +22,7 @@ fun getMarkerTint(
|
||||
}
|
||||
|
||||
class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
private val animatingMarkers = hashMapOf<String, ValueAnimator>()
|
||||
private val animatingMarkers = hashMapOf<Marker, ValueAnimator>()
|
||||
|
||||
fun animateMarkerAppear(
|
||||
marker: Marker,
|
||||
@@ -30,9 +30,9 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
highlight: Boolean,
|
||||
fault: Boolean
|
||||
) {
|
||||
animatingMarkers[marker.id]?.let {
|
||||
animatingMarkers[marker]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofInt(0, 20).apply {
|
||||
@@ -48,15 +48,14 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
fault = fault
|
||||
)
|
||||
)
|
||||
marker.isVisible = true
|
||||
}
|
||||
addListener(onEnd = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}, onCancel = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker.id] = anim
|
||||
animatingMarkers[marker] = anim
|
||||
anim.start()
|
||||
}
|
||||
|
||||
@@ -66,9 +65,9 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
highlight: Boolean,
|
||||
fault: Boolean
|
||||
) {
|
||||
animatingMarkers[marker.id]?.let {
|
||||
animatingMarkers[marker]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofInt(20, 0).apply {
|
||||
@@ -87,28 +86,28 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
}
|
||||
addListener(onEnd = {
|
||||
marker.remove()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}, onCancel = {
|
||||
marker.remove()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker.id] = anim
|
||||
animatingMarkers[marker] = anim
|
||||
anim.start()
|
||||
}
|
||||
|
||||
fun deleteMarker(marker: Marker) {
|
||||
animatingMarkers[marker.id]?.let {
|
||||
animatingMarkers[marker]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}
|
||||
marker.remove()
|
||||
}
|
||||
|
||||
fun animateMarkerBounce(marker: Marker) {
|
||||
animatingMarkers[marker.id]?.let {
|
||||
animatingMarkers[marker]?.let {
|
||||
it.cancel()
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}
|
||||
|
||||
val anim = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
@@ -119,12 +118,12 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
|
||||
marker.setAnchor(0.5f, 1.0f + t)
|
||||
}
|
||||
addListener(onEnd = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
}, onCancel = {
|
||||
animatingMarkers.remove(marker.id)
|
||||
animatingMarkers.remove(marker)
|
||||
})
|
||||
}
|
||||
animatingMarkers[marker.id] = anim
|
||||
animatingMarkers[marker] = anim
|
||||
anim.start()
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
import com.google.android.libraries.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLng
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -2,9 +2,9 @@ package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
import com.google.android.libraries.maps.GoogleMap
|
||||
import com.google.android.libraries.maps.model.LatLngBounds
|
||||
import com.google.android.libraries.places.api.model.Place
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -20,6 +20,8 @@ import java.io.IOException
|
||||
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float)
|
||||
|
||||
data class PlaceWithBounds(val latLng: LatLng, val viewport: LatLngBounds?)
|
||||
|
||||
internal fun getClusterDistance(zoom: Float): Int? {
|
||||
return when (zoom) {
|
||||
in 0.0..7.0 -> 100
|
||||
@@ -152,13 +154,13 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
db.chargeLocationsDao().getAllChargeLocations()
|
||||
}
|
||||
|
||||
val searchResult: MutableLiveData<Place> by lazy {
|
||||
MutableLiveData<Place>()
|
||||
val searchResult: MutableLiveData<PlaceWithBounds> by lazy {
|
||||
MutableLiveData<PlaceWithBounds>()
|
||||
}
|
||||
|
||||
val mapType: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData<Int>().apply {
|
||||
value = GoogleMap.MAP_TYPE_NORMAL
|
||||
val mapType: MutableLiveData<AnyMap.Type> by lazy {
|
||||
MutableLiveData<AnyMap.Type>().apply {
|
||||
value = AnyMap.Type.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +179,7 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
}
|
||||
|
||||
fun setMapType(type: Int) {
|
||||
fun setMapType(type: AnyMap.Type) {
|
||||
mapType.value = type
|
||||
}
|
||||
|
||||
|
||||
12
app/src/main/res/drawable/ic_map_marker.xml
Normal file
12
app/src/main/res/drawable/ic_map_marker.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector android:height="44.11976dp"
|
||||
android:viewportHeight="368.4"
|
||||
android:viewportWidth="233.8"
|
||||
android:width="28dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#00e676"
|
||||
android:pathData="M109.8,0h13.6c33.9,1.9 67.1,18.5 87.7,45.8c13.5,17.2 21,38.6 22.7,60.3v8.1c-0.8,42.1 -27.7,76.6 -51,109.4c-26.2,37 -50.4,77.3 -57.1,122.9c-1.8,7.7 0.4,18.5 -8.9,22c-2.2,-1.7 -4.7,-3.1 -6.2,-5.4c-2.7,-25.5 -9.1,-50.7 -20,-73.9c-12.3,-27.1 -29.5,-51.6 -47,-75.6C33,199 23,184.2 14.7,168.3c-13,-23.8 -17.9,-51.9 -12.5,-78.6c4.4,-21.1 15.4,-40.6 30.6,-55.7C53.3,14 81.1,1.8 109.8,0zM107.2,74.1c-16.1,3.6 -29.6,17.8 -31,34.5c-1.7,15.5 7.4,31.3 21.3,38.1c15.1,8 34.9,5.5 47.5,-6c11.8,-10.4 16,-28.3 9.9,-42.9C147.6,79.7 126,69.3 107.2,74.1z" />
|
||||
<path
|
||||
android:fillColor="#007e41"
|
||||
android:pathData="M107.2,74.1c18.9,-4.8 40.4,5.5 47.7,23.7c6.1,14.5 1.9,32.5 -9.9,42.9c-12.6,11.5 -32.4,14 -47.5,6c-13.9,-6.8 -23,-22.6 -21.3,-38.1C77.6,92 91.1,77.7 107.2,74.1z" />
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_paypal.xml
Normal file
9
app/src/main/res/drawable/ic_paypal.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<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="M20.055,7.713c-0.677,-0.842 -1.673,-1.41 -2.764,-1.615C16.773,3.971 14.877,3 13.045,3H7.057C6.425,3 5.878,3.409 5.689,4.009c-0.015,0.04 -0.026,0.082 -0.036,0.125L3.034,16.262c-0.009,0.041 -0.015,0.083 -0.019,0.125C3.006,16.449 3,16.513 3,16.56C3,17.354 3.648,18 4.444,18h2.316l-0.267,1.262c-0.008,0.04 -0.014,0.081 -0.018,0.121c-0.009,0.063 -0.016,0.126 -0.016,0.173C6.461,20.353 7.109,21 7.905,21h3.259c0.056,0 0.111,-0.005 0.166,-0.015c0.549,-0.063 1.011,-0.437 1.191,-0.963c0.021,-0.05 0.038,-0.103 0.05,-0.156L13.475,16h1.398c3.365,0 5.38,-1.445 5.989,-4.295C21.278,9.752 20.653,8.456 20.055,7.713zM5.137,16L7.512,5h5.533c0.293,0 1.5,0.061 2.078,1.013h-4.706c-0.626,0 -1.17,0.401 -1.363,0.99c-0.019,0.049 -0.033,0.1 -0.043,0.151l-1.034,5.093L7.183,16H5.137zM18.906,11.287C18.5,13.188 17.293,14 14.873,14h-1.857c-0.823,0 -1.338,0.652 -1.405,1.198L10.721,19H8.594l1.271,-6h1.444c4.259,0 5.665,-2.394 6.094,-4.402c0.027,-0.128 0.045,-0.256 0.057,-0.382c0.378,0.151 0.749,0.393 1.038,0.751C18.971,9.557 19.108,10.337 18.906,11.287z"
|
||||
android:fillColor="#000000" />
|
||||
</vector>
|
||||
39
app/src/main/res/layout/amu_text_bubble.xml
Normal file
39
app/src/main/res/layout/amu_text_bubble.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2020 Google Inc.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.maps.android.ui.RotationLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@id/amu_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingTop="5dp" />
|
||||
|
||||
</com.google.maps.android.ui.RotationLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -19,12 +19,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<fragment
|
||||
<FrameLayout
|
||||
android:id="@+id/map"
|
||||
android:name="com.google.android.libraries.maps.SupportMapFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MapsActivity" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/toolbar_container"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:contentDescription="@{item.contentDescription}"
|
||||
android:tint="?colorPrimary"
|
||||
app:tint="?colorPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@{item.icon}"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:contentDescription="@{item.contentDescription}"
|
||||
android:tint="?colorPrimary"
|
||||
app:tint="?colorPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@{item.icon}"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<import type="net.vonforst.evmap.viewmodel.MapViewModel" />
|
||||
|
||||
<import type="com.google.android.libraries.maps.GoogleMap" />
|
||||
<import type="com.car2go.maps.AnyMap" />
|
||||
|
||||
<variable
|
||||
name="vm"
|
||||
@@ -51,24 +51,24 @@
|
||||
android:id="@+id/rbStandard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(GoogleMap.MAP_TYPE_NORMAL)}"
|
||||
android:onClick="@{() -> vm.setMapType(GoogleMap.MAP_TYPE_NORMAL)}"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.NORMAL)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.NORMAL)}"
|
||||
android:text="@string/map_type_normal" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSatellite"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(GoogleMap.MAP_TYPE_HYBRID)}"
|
||||
android:onClick="@{() -> vm.setMapType(GoogleMap.MAP_TYPE_HYBRID)}"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.HYBRID)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.HYBRID)}"
|
||||
android:text="@string/map_type_satellite" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbTerrain"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(GoogleMap.MAP_TYPE_TERRAIN)}"
|
||||
android:onClick="@{() -> vm.setMapType(GoogleMap.MAP_TYPE_TERRAIN)}"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.TERRAIN)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.TERRAIN)}"
|
||||
android:text="@string/map_type_terrain" />
|
||||
</RadioGroup>
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
[
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#242f3e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "all",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"lightness": -80
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative.locality",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#263c3f"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#6b9a76"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#2b3544"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#9ca5b3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#38414e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.arterial",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#212a37"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#1f2835"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#f3d19c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.local",
|
||||
"elementType": "geometry.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#38414e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.local",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#212a37"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#2f3948"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit.station",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#17263c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#515c6d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"lightness": -20
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -34,6 +34,8 @@
|
||||
<string name="github_link_title">Quellcode</string>
|
||||
<string name="oss_licenses">Open Source-Lizenzen</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="settings_ui">Oberfläche</string>
|
||||
<string name="settings_map">Karte</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="copyright_summary">©2020 Johan von Forstner</string>
|
||||
<string name="other">Sonstiges</string>
|
||||
@@ -66,7 +68,6 @@
|
||||
<string name="show_less">weniger…</string>
|
||||
<string name="favorites_empty_state">Wenn du Ladestationen als Favorit markierst, tauchen sie hier auf.</string>
|
||||
<string name="donate">Spenden</string>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 30% Gebühren ab.</string>
|
||||
<string name="donation_successful">Vielen Dank! ❤️</string>
|
||||
<string name="donation_failed">Etwas ist schiefgelaufen. 😕</string>
|
||||
<string name="map_type_normal">Standard</string>
|
||||
@@ -101,6 +102,7 @@
|
||||
<string name="filter_exclude_faults">Ladesäulen mit Störung ausschließen</string>
|
||||
<string name="charge_cards">Ladetarife</string>
|
||||
<string name="and_n_others">und %d weitere</string>
|
||||
<string name="pref_map_provider">Kartenanbieter</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d kompatibler Ladetarif</item>
|
||||
<item quantity="other">%d kompatible Ladetarife</item>
|
||||
|
||||
4
app/src/main/res/values/ids.xml
Normal file
4
app/src/main/res/values/ids.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="amu_text" type="id" />
|
||||
</resources>
|
||||
@@ -33,6 +33,8 @@
|
||||
<string name="github_link_title">Source code</string>
|
||||
<string name="oss_licenses">Open Source Licenses</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_ui">User Interface</string>
|
||||
<string name="settings_map">Map</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="copyright_summary">©2020 Johan von Forstner</string>
|
||||
<string name="other">Other</string>
|
||||
@@ -65,7 +67,6 @@
|
||||
<string name="show_less">less…</string>
|
||||
<string name="favorites_empty_state">If you add chargers as favorites, they will show up here.</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.\n\nGoogle takes 30% off every donation.</string>
|
||||
<string name="donation_successful">Thank you! ❤️</string>
|
||||
<string name="donation_failed">Something went wrong. 😕</string>
|
||||
<string name="map_type_normal">Default</string>
|
||||
@@ -100,6 +101,7 @@
|
||||
<string name="filter_exclude_faults">Exclude chargers with reported faults</string>
|
||||
<string name="charge_cards">Payment methods</string>
|
||||
<string name="and_n_others">and %d others</string>
|
||||
<string name="pref_map_provider">Map provider</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d compatible payment method</item>
|
||||
<item quantity="other">%d compatible payment methods</item>
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory android:title="@string/settings">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="navigate_use_maps"
|
||||
android:title="@string/pref_navigate_use_maps"
|
||||
android:summaryOn="@string/pref_navigate_use_maps_on"
|
||||
android:summaryOff="@string/pref_navigate_use_maps_off"
|
||||
android:defaultValue="true" />
|
||||
<PreferenceCategory android:title="@string/settings_ui">
|
||||
|
||||
<ListPreference
|
||||
android:key="language"
|
||||
@@ -27,4 +20,22 @@
|
||||
android:summary="@string/pref_darkmode_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/settings_map">
|
||||
|
||||
<ListPreference
|
||||
android:key="map_provider"
|
||||
android:title="@string/pref_map_provider"
|
||||
android:entries="@array/pref_map_provider_names"
|
||||
android:entryValues="@array/pref_map_provider_values"
|
||||
android:defaultValue="@string/pref_map_provider_default"
|
||||
android:summary="%s" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="navigate_use_maps"
|
||||
android:title="@string/pref_navigate_use_maps"
|
||||
android:summaryOn="@string/pref_navigate_use_maps_on"
|
||||
android:summaryOff="@string/pref_navigate_use_maps_off"
|
||||
android:defaultValue="true" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -8,7 +8,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libs_version"
|
||||
|
||||
@@ -25,6 +25,7 @@ allprojects {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user