mirror of
https://github.com/ev-map/EVMap.git
synced 2026-02-02 03:01:45 -05:00
move stuff to fragment
This commit is contained in:
@@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
@@ -58,6 +59,7 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.4"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
@@ -73,6 +75,16 @@ dependencies {
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
implementation 'com.github.MikeOrtiz:TouchImageView:2.3.3'
|
||||
|
||||
// navigation library
|
||||
def nav_version = "2.3.0-alpha04"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
// viewmodel library
|
||||
def lifecycle_version = "2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// debug tools
|
||||
implementation 'com.facebook.stetho:stetho:1.5.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.viewpager2.widget.ViewPager2
|
||||
import com.johan.evmap.adapter.GalleryAdapter
|
||||
import com.johan.evmap.adapter.galleryTransitionName
|
||||
import com.johan.evmap.databinding.ActivityGalleryBinding
|
||||
import com.johan.evmap.fragment.MapFragment
|
||||
import com.ortiz.touchview.TouchImageView
|
||||
|
||||
|
||||
@@ -68,8 +69,8 @@ class GalleryActivity : AppCompatActivity() {
|
||||
override fun finishAfterTransition() {
|
||||
isReturning = true
|
||||
val data = Intent()
|
||||
data.putExtra(MapsActivity.EXTRA_STARTING_GALLERY_POSITION, startingPosition)
|
||||
data.putExtra(MapsActivity.EXTRA_CURRENT_GALLERY_POSITION, currentPosition)
|
||||
data.putExtra(MapFragment.EXTRA_STARTING_GALLERY_POSITION, startingPosition)
|
||||
data.putExtra(MapFragment.EXTRA_CURRENT_GALLERY_POSITION, currentPosition)
|
||||
setResult(Activity.RESULT_OK, data)
|
||||
super.finishAfterTransition()
|
||||
}
|
||||
|
||||
@@ -1,424 +1,70 @@
|
||||
package com.johan.evmap
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.app.SharedElementCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.gms.maps.CameraUpdateFactory
|
||||
import com.google.android.gms.maps.GoogleMap
|
||||
import com.google.android.gms.maps.OnMapReadyCallback
|
||||
import com.google.android.gms.maps.SupportMapFragment
|
||||
import com.google.android.gms.maps.model.*
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.johan.evmap.adapter.ConnectorAdapter
|
||||
import com.johan.evmap.adapter.DetailAdapter
|
||||
import com.johan.evmap.adapter.GalleryAdapter
|
||||
import com.johan.evmap.adapter.galleryTransitionName
|
||||
import com.johan.evmap.api.*
|
||||
import com.johan.evmap.databinding.ActivityMapsBinding
|
||||
import com.johan.evmap.ui.ClusterIconGenerator
|
||||
import com.johan.evmap.ui.getBitmapDescriptor
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import com.johan.evmap.api.ChargeLocation
|
||||
|
||||
const val REQUEST_LOCATION_PERMISSION = 1
|
||||
|
||||
class MapsActivityViewModel : ViewModel() {
|
||||
val chargepoints: MutableLiveData<List<ChargepointListItem>> by lazy {
|
||||
MutableLiveData<List<ChargepointListItem>>().apply {
|
||||
value = emptyList()
|
||||
}
|
||||
}
|
||||
val charger: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
}
|
||||
val availability: MutableLiveData<ChargeLocationStatus> by lazy {
|
||||
MutableLiveData<ChargeLocationStatus>()
|
||||
}
|
||||
val myLocationEnabled: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>()
|
||||
}
|
||||
}
|
||||
|
||||
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
private lateinit var binding: ActivityMapsBinding
|
||||
private var map: GoogleMap? = null
|
||||
private lateinit var api: GoingElectricApi
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private val vm: MapsActivityViewModel by viewModels()
|
||||
private var markers: Map<Marker, ChargeLocation> = emptyMap()
|
||||
private var clusterMarkers: List<Marker> = emptyList()
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
|
||||
|
||||
private var reenterState: Bundle? = null
|
||||
class MapsActivity : AppCompatActivity() {
|
||||
private lateinit var navController: NavController
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
api = GoingElectricApi.create(getString(R.string.goingelectric_key))
|
||||
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
setContentView(R.layout.activity_maps)
|
||||
|
||||
ActivityCompat.setExitSharedElementCallback(this, exitElementCallback)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
title = ""
|
||||
//ActivityCompat.setExitSharedElementCallback(this, exitElementCallback)
|
||||
//setSupportActionBar(binding.toolbar)
|
||||
//title = ""
|
||||
|
||||
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
|
||||
mapFragment.getMapAsync(this)
|
||||
|
||||
setupAdapters()
|
||||
|
||||
bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(binding.bottomSheet)
|
||||
|
||||
vm.charger.observe(this, object : Observer<ChargeLocation> {
|
||||
var previousCharger = vm.charger.value
|
||||
|
||||
override fun onChanged(charger: ChargeLocation?) {
|
||||
if (charger != null) {
|
||||
if (previousCharger == null ||
|
||||
previousCharger!!.id != charger.id
|
||||
) {
|
||||
vm.availability.value = null
|
||||
bottomSheetBehavior.state =
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
loadChargerDetails()
|
||||
}
|
||||
} else {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
}
|
||||
previousCharger = charger
|
||||
}
|
||||
})
|
||||
vm.chargepoints.observe(this, Observer {
|
||||
updateMap(it)
|
||||
})
|
||||
|
||||
binding.fabLocate.setOnClickListener {
|
||||
if (!hasLocationPermission()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
REQUEST_LOCATION_PERMISSION
|
||||
)
|
||||
} else {
|
||||
enableLocation(true)
|
||||
}
|
||||
}
|
||||
binding.fabDirections.setOnClickListener {
|
||||
val charger = vm.charger.value
|
||||
if (charger != null) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val coord = charger.coordinates
|
||||
|
||||
// google maps navigation
|
||||
intent.data = Uri.parse("google.navigation:q=${coord.lat},${coord.lng}")
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
// fallback: generic geo intent
|
||||
intent.data = Uri.parse("geo:${coord.lat},${coord.lng}")
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Snackbar.make(
|
||||
binding.root,
|
||||
R.string.no_maps_app_found,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.detailView.goingelectricButton.setOnClickListener {
|
||||
val charger = vm.charger.value
|
||||
if (charger != null) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https:${charger.url}"))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
binding.detailView.topPart.setOnClickListener {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapters() {
|
||||
val galleryClickListener = object : GalleryAdapter.ItemClickListener {
|
||||
override fun onItemClick(view: View, position: Int) {
|
||||
val photos = vm.charger.value?.photos ?: return
|
||||
val intent = Intent(this@MapsActivity, GalleryActivity::class.java).apply {
|
||||
putExtra(GalleryActivity.EXTRA_PHOTOS, ArrayList<ChargerPhoto>(photos))
|
||||
putExtra(GalleryActivity.EXTRA_POSITION, position)
|
||||
}
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
this@MapsActivity, view, view.transitionName
|
||||
)
|
||||
startActivity(intent, options.toBundle())
|
||||
}
|
||||
}
|
||||
|
||||
binding.gallery.apply {
|
||||
adapter = GalleryAdapter(this@MapsActivity, galleryClickListener)
|
||||
layoutManager =
|
||||
LinearLayoutManager(this@MapsActivity, LinearLayoutManager.HORIZONTAL, false)
|
||||
addItemDecoration(DividerItemDecoration(
|
||||
this@MapsActivity, LinearLayoutManager.HORIZONTAL
|
||||
).apply {
|
||||
setDrawable(getDrawable(R.drawable.gallery_divider)!!)
|
||||
})
|
||||
}
|
||||
|
||||
binding.detailView.connectors.apply {
|
||||
adapter = ConnectorAdapter()
|
||||
layoutManager =
|
||||
LinearLayoutManager(this@MapsActivity, LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
|
||||
binding.detailView.details.apply {
|
||||
adapter = DetailAdapter()
|
||||
layoutManager =
|
||||
LinearLayoutManager(this@MapsActivity, LinearLayoutManager.VERTICAL, false)
|
||||
addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
this@MapsActivity,
|
||||
LinearLayoutManager.VERTICAL
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
this.map = map
|
||||
map.setOnCameraIdleListener {
|
||||
loadChargepoints()
|
||||
}
|
||||
map.setOnMarkerClickListener { marker ->
|
||||
when (marker) {
|
||||
in markers -> {
|
||||
vm.charger.value = markers[marker]
|
||||
true
|
||||
}
|
||||
in clusterMarkers -> {
|
||||
val newZoom = map.cameraPosition.zoom + 2
|
||||
map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.position, newZoom))
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
map.setOnMapClickListener {
|
||||
vm.charger.value = null
|
||||
}
|
||||
|
||||
val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
map.setMapStyle(
|
||||
if (mode == Configuration.UI_MODE_NIGHT_YES) {
|
||||
MapStyleOptions.loadRawResourceStyle(this, R.raw.maps_night_mode)
|
||||
} else null
|
||||
)
|
||||
|
||||
|
||||
if (hasLocationPermission()) {
|
||||
enableLocation(false)
|
||||
} else {
|
||||
// center the camera on Europe
|
||||
val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(50.113388, 9.252536), 3.5f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun enableLocation(animate: Boolean) {
|
||||
val map = this.map ?: return
|
||||
map.isMyLocationEnabled = true
|
||||
vm.myLocationEnabled.value = true
|
||||
map.uiSettings.isMyLocationButtonEnabled = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLocationPermission() =
|
||||
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
|
||||
private fun loadChargepoints() {
|
||||
val map = this.map ?: return
|
||||
val bounds = map.projection.visibleRegion.latLngBounds
|
||||
api.getChargepoints(
|
||||
bounds.southwest.latitude, bounds.southwest.longitude,
|
||||
bounds.northeast.latitude, bounds.northeast.longitude,
|
||||
clustering = map.cameraPosition.zoom < 12, zoom = map.cameraPosition.zoom,
|
||||
clusterDistance = 70
|
||||
).enqueue(object : Callback<ChargepointList> {
|
||||
override fun onFailure(call: Call<ChargepointList>, t: Throwable) {
|
||||
//TODO: show error message
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<ChargepointList>,
|
||||
response: Response<ChargepointList>
|
||||
) {
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
//TODO: show error message
|
||||
return
|
||||
}
|
||||
|
||||
vm.chargepoints.value = response.body()!!.chargelocations
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadChargerDetails() {
|
||||
val charger = vm.charger.value ?: return
|
||||
api.getChargepointDetail(charger.id).enqueue(object : Callback<ChargepointList> {
|
||||
override fun onFailure(call: Call<ChargepointList>, t: Throwable) {
|
||||
//TODO: show error message
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<ChargepointList>,
|
||||
response: Response<ChargepointList>
|
||||
) {
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
//TODO: show error message
|
||||
return
|
||||
}
|
||||
|
||||
vm.charger.value = response.body()!!.chargelocations[0] as ChargeLocation
|
||||
}
|
||||
})
|
||||
|
||||
lifecycleScope.launch {
|
||||
loadChargerAvailability(charger)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadChargerAvailability(charger: ChargeLocation) {
|
||||
var availability: ChargeLocationStatus? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
for (ad in availabilityDetectors) {
|
||||
try {
|
||||
availability = ad.getAvailability(charger)
|
||||
break
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: AvailabilityDetectorException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
vm.availability.value = availability
|
||||
}
|
||||
|
||||
private fun updateMap(chargepoints: List<ChargepointListItem>) {
|
||||
val map = this.map ?: return
|
||||
markers.keys.forEach { it.remove() }
|
||||
clusterMarkers.forEach { it.remove() }
|
||||
|
||||
val iconGenerator = ClusterIconGenerator(this)
|
||||
val clusters = chargepoints.filterIsInstance<ChargeLocationCluster>()
|
||||
val chargers = chargepoints.filterIsInstance<ChargeLocation>()
|
||||
|
||||
markers = chargers.map { charger ->
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.icon(
|
||||
getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging, when {
|
||||
charger.maxPower >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower >= 11 -> R.color.charger_11kw
|
||||
else -> R.color.charger_low
|
||||
}, this
|
||||
)
|
||||
)
|
||||
) to charger
|
||||
}.toMap()
|
||||
clusterMarkers = clusters.map { cluster ->
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(cluster.coordinates.lat, cluster.coordinates.lng))
|
||||
.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(cluster.clusterCount.toString())))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_LOCATION_PERMISSION -> {
|
||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
enableLocation(true)
|
||||
}
|
||||
}
|
||||
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
navController = findNavController(R.id.nav_host_fragment)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (bottomSheetBehavior.state != BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED &&
|
||||
bottomSheetBehavior.state != BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
) {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
} else if (bottomSheetBehavior.state == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED) {
|
||||
vm.charger.value = null
|
||||
|
||||
}
|
||||
|
||||
fun navigateTo(charger: ChargeLocation) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val coord = charger.coordinates
|
||||
|
||||
// google maps navigation
|
||||
intent.data = Uri.parse("google.navigation:q=${coord.lat},${coord.lng}")
|
||||
val pm = packageManager
|
||||
if (intent.resolveActivity(pm) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
// fallback: generic geo intent
|
||||
intent.data = Uri.parse("geo:${coord.lat},${coord.lng}")
|
||||
if (intent.resolveActivity(pm) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
// TODO:
|
||||
/*
|
||||
Snackbar.make(
|
||||
,
|
||||
R.string.no_maps_app_found,
|
||||
Snackbar.LENGTH_SHORT
|
||||
)*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val exitElementCallback = object : SharedElementCallback() {
|
||||
|
||||
/*private val exitElementCallback = object : SharedElementCallback() {
|
||||
override fun onMapSharedElements(
|
||||
names: MutableList<String>,
|
||||
sharedElements: MutableMap<String, View>
|
||||
) {
|
||||
if (reenterState != null) {
|
||||
val startingPosition = reenterState!!.getInt(EXTRA_STARTING_GALLERY_POSITION)
|
||||
val currentPosition = reenterState!!.getInt(EXTRA_CURRENT_GALLERY_POSITION)
|
||||
val startingPosition = reenterState!!.getInt(MapFragment.EXTRA_STARTING_GALLERY_POSITION)
|
||||
val currentPosition = reenterState!!.getInt(MapFragment.EXTRA_CURRENT_GALLERY_POSITION)
|
||||
if (startingPosition != currentPosition) {
|
||||
// Current element has changed, need to override previous exit transitions
|
||||
val newTransitionName = galleryTransitionName(currentPosition)
|
||||
@@ -435,15 +81,15 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
reenterState = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
override fun onActivityReenter(resultCode: Int, data: Intent) {
|
||||
/*override fun onActivityReenter(resultCode: Int, data: Intent) {
|
||||
// returning to gallery
|
||||
super.onActivityReenter(resultCode, data)
|
||||
reenterState = Bundle(data.extras)
|
||||
reenterState?.let {
|
||||
val startingPosition = it.getInt(EXTRA_STARTING_GALLERY_POSITION)
|
||||
val currentPosition = it.getInt(EXTRA_CURRENT_GALLERY_POSITION)
|
||||
val startingPosition = it.getInt(MapFragment.EXTRA_STARTING_GALLERY_POSITION)
|
||||
val currentPosition = it.getInt(MapFragment.EXTRA_CURRENT_GALLERY_POSITION)
|
||||
if (startingPosition != currentPosition) binding.gallery.scrollToPosition(
|
||||
currentPosition
|
||||
)
|
||||
@@ -458,10 +104,5 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_STARTING_GALLERY_POSITION = "extra_starting_item_position"
|
||||
const val EXTRA_CURRENT_GALLERY_POSITION = "extra_current_item_position"
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
422
app/src/main/java/com/johan/evmap/fragment/MapFragment.kt
Normal file
422
app/src/main/java/com/johan/evmap/fragment/MapFragment.kt
Normal file
@@ -0,0 +1,422 @@
|
||||
package com.johan.evmap.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.*
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
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.gms.maps.CameraUpdateFactory
|
||||
import com.google.android.gms.maps.GoogleMap
|
||||
import com.google.android.gms.maps.OnMapReadyCallback
|
||||
import com.google.android.gms.maps.SupportMapFragment
|
||||
import com.google.android.gms.maps.model.*
|
||||
import com.johan.evmap.GalleryActivity
|
||||
import com.johan.evmap.MapsActivity
|
||||
import com.johan.evmap.R
|
||||
import com.johan.evmap.REQUEST_LOCATION_PERMISSION
|
||||
import com.johan.evmap.adapter.ConnectorAdapter
|
||||
import com.johan.evmap.adapter.DetailAdapter
|
||||
import com.johan.evmap.adapter.GalleryAdapter
|
||||
import com.johan.evmap.api.*
|
||||
import com.johan.evmap.databinding.FragmentMapBinding
|
||||
import com.johan.evmap.ui.ClusterIconGenerator
|
||||
import com.johan.evmap.ui.getBitmapDescriptor
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class MapViewModel : ViewModel() {
|
||||
val chargepoints: MutableLiveData<List<ChargepointListItem>> by lazy {
|
||||
MutableLiveData<List<ChargepointListItem>>().apply {
|
||||
value = emptyList()
|
||||
}
|
||||
}
|
||||
val charger: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
}
|
||||
val availability: MutableLiveData<ChargeLocationStatus> by lazy {
|
||||
MutableLiveData<ChargeLocationStatus>()
|
||||
}
|
||||
val myLocationEnabled: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>()
|
||||
}
|
||||
}
|
||||
|
||||
class MapFragment : Fragment(), OnMapReadyCallback {
|
||||
private lateinit var binding: FragmentMapBinding
|
||||
private val vm: MapViewModel by viewModels()
|
||||
private var map: GoogleMap? = null
|
||||
private lateinit var api: GoingElectricApi
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
|
||||
private var markers: Map<Marker, ChargeLocation> = emptyMap()
|
||||
private var clusterMarkers: List<Marker> = emptyList()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_map, container, false)
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
api = GoingElectricApi.create(getString(R.string.goingelectric_key))
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
|
||||
mapFragment.getMapAsync(this)
|
||||
bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(binding.bottomSheet)
|
||||
|
||||
setupObservers()
|
||||
setupClickListeners()
|
||||
setupAdapters()
|
||||
|
||||
val navController = findNavController()
|
||||
val appBarConfiguration = AppBarConfiguration(navController.graph)
|
||||
|
||||
view.findViewById<Toolbar>(R.id.toolbar)
|
||||
.setupWithNavController(navController, appBarConfiguration)
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
binding.fabLocate.setOnClickListener {
|
||||
if (!hasLocationPermission()) {
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
REQUEST_LOCATION_PERMISSION
|
||||
)
|
||||
} else {
|
||||
enableLocation(true)
|
||||
}
|
||||
}
|
||||
binding.fabDirections.setOnClickListener {
|
||||
val charger = vm.charger.value
|
||||
if (charger != null) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
(requireActivity() as MapsActivity).navigateTo(charger)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.detailView.goingelectricButton.setOnClickListener {
|
||||
val charger = vm.charger.value
|
||||
if (charger != null) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https:${charger.url}"))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
binding.detailView.topPart.setOnClickListener {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
vm.charger.observe(viewLifecycleOwner, object : Observer<ChargeLocation> {
|
||||
var previousCharger = vm.charger.value
|
||||
|
||||
override fun onChanged(charger: ChargeLocation?) {
|
||||
if (charger != null) {
|
||||
if (previousCharger == null ||
|
||||
previousCharger!!.id != charger.id
|
||||
) {
|
||||
vm.availability.value = null
|
||||
bottomSheetBehavior.state =
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
loadChargerDetails()
|
||||
}
|
||||
} else {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
}
|
||||
previousCharger = charger
|
||||
}
|
||||
})
|
||||
vm.chargepoints.observe(viewLifecycleOwner, Observer {
|
||||
updateMap(it)
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupAdapters() {
|
||||
val galleryClickListener = object : GalleryAdapter.ItemClickListener {
|
||||
override fun onItemClick(view: View, position: Int) {
|
||||
val photos = vm.charger.value?.photos ?: return
|
||||
val intent = Intent(context, GalleryActivity::class.java).apply {
|
||||
putExtra(GalleryActivity.EXTRA_PHOTOS, ArrayList<ChargerPhoto>(photos))
|
||||
putExtra(GalleryActivity.EXTRA_POSITION, position)
|
||||
}
|
||||
// TODO:
|
||||
/*val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
activity, view, view.transitionName
|
||||
)
|
||||
startActivity(intent, options.toBundle())*/
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
binding.gallery.apply {
|
||||
adapter = GalleryAdapter(context, galleryClickListener)
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
context, LinearLayoutManager.HORIZONTAL
|
||||
).apply {
|
||||
setDrawable(context.getDrawable(R.drawable.gallery_divider)!!)
|
||||
})
|
||||
}
|
||||
|
||||
binding.detailView.connectors.apply {
|
||||
adapter = ConnectorAdapter()
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
|
||||
binding.detailView.details.apply {
|
||||
adapter = DetailAdapter()
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
this.map = map
|
||||
map.setOnCameraIdleListener {
|
||||
loadChargepoints()
|
||||
}
|
||||
map.setOnMarkerClickListener { marker ->
|
||||
when (marker) {
|
||||
in markers -> {
|
||||
vm.charger.value = markers[marker]
|
||||
true
|
||||
}
|
||||
in clusterMarkers -> {
|
||||
val newZoom = map.cameraPosition.zoom + 2
|
||||
map.animateCamera(CameraUpdateFactory.newLatLngZoom(marker.position, newZoom))
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
map.setOnMapClickListener {
|
||||
vm.charger.value = null
|
||||
}
|
||||
|
||||
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 (hasLocationPermission()) {
|
||||
enableLocation(false)
|
||||
} else {
|
||||
// center the camera on Europe
|
||||
val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(50.113388, 9.252536), 3.5f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun enableLocation(animate: Boolean) {
|
||||
val map = this.map ?: return
|
||||
map.isMyLocationEnabled = true
|
||||
vm.myLocationEnabled.value = true
|
||||
map.uiSettings.isMyLocationButtonEnabled = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLocationPermission(): Boolean {
|
||||
val context = context ?: return false
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun loadChargepoints() {
|
||||
val map = this.map ?: return
|
||||
val bounds = map.projection.visibleRegion.latLngBounds
|
||||
api.getChargepoints(
|
||||
bounds.southwest.latitude, bounds.southwest.longitude,
|
||||
bounds.northeast.latitude, bounds.northeast.longitude,
|
||||
clustering = map.cameraPosition.zoom < 12, zoom = map.cameraPosition.zoom,
|
||||
clusterDistance = 70
|
||||
).enqueue(object : Callback<ChargepointList> {
|
||||
override fun onFailure(call: Call<ChargepointList>, t: Throwable) {
|
||||
//TODO: show error message
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<ChargepointList>,
|
||||
response: Response<ChargepointList>
|
||||
) {
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
//TODO: show error message
|
||||
return
|
||||
}
|
||||
|
||||
vm.chargepoints.value = response.body()!!.chargelocations
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun loadChargerDetails() {
|
||||
val charger = vm.charger.value ?: return
|
||||
api.getChargepointDetail(charger.id).enqueue(object : Callback<ChargepointList> {
|
||||
override fun onFailure(call: Call<ChargepointList>, t: Throwable) {
|
||||
//TODO: show error message
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<ChargepointList>,
|
||||
response: Response<ChargepointList>
|
||||
) {
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
//TODO: show error message
|
||||
return
|
||||
}
|
||||
|
||||
vm.charger.value = response.body()!!.chargelocations[0] as ChargeLocation
|
||||
}
|
||||
})
|
||||
|
||||
lifecycleScope.launch {
|
||||
loadChargerAvailability(charger)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadChargerAvailability(charger: ChargeLocation) {
|
||||
var availability: ChargeLocationStatus? = null
|
||||
withContext(Dispatchers.IO) {
|
||||
for (ad in availabilityDetectors) {
|
||||
try {
|
||||
availability = ad.getAvailability(charger)
|
||||
break
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: AvailabilityDetectorException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
vm.availability.value = availability
|
||||
}
|
||||
|
||||
private fun updateMap(chargepoints: List<ChargepointListItem>) {
|
||||
val map = this.map ?: return
|
||||
markers.keys.forEach { it.remove() }
|
||||
clusterMarkers.forEach { it.remove() }
|
||||
|
||||
val context = context ?: return
|
||||
val iconGenerator = ClusterIconGenerator(context)
|
||||
val clusters = chargepoints.filterIsInstance<ChargeLocationCluster>()
|
||||
val chargers = chargepoints.filterIsInstance<ChargeLocation>()
|
||||
|
||||
markers = chargers.map { charger ->
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.icon(
|
||||
getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging, when {
|
||||
charger.maxPower >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower >= 11 -> R.color.charger_11kw
|
||||
else -> R.color.charger_low
|
||||
}, context
|
||||
)
|
||||
)
|
||||
) to charger
|
||||
}.toMap()
|
||||
clusterMarkers = clusters.map { cluster ->
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(cluster.coordinates.lat, cluster.coordinates.lng))
|
||||
.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(cluster.clusterCount.toString())))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_LOCATION_PERMISSION -> {
|
||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
enableLocation(true)
|
||||
}
|
||||
}
|
||||
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
|
||||
fun goBack(): Boolean {
|
||||
if (bottomSheetBehavior.state != BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED &&
|
||||
bottomSheetBehavior.state != BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
) {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
return true
|
||||
} else if (bottomSheetBehavior.state == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED) {
|
||||
vm.charger.value = null
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val EXTRA_STARTING_GALLERY_POSITION = "extra_starting_item_position"
|
||||
const val EXTRA_CURRENT_GALLERY_POSITION = "extra_current_item_position"
|
||||
}
|
||||
}
|
||||
@@ -1,111 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.johan.evmap.MapsActivityViewModel" />
|
||||
|
||||
<variable
|
||||
name="vm"
|
||||
type="MapsActivityViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
android:layout_width="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/map"
|
||||
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MapsActivity" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:elevation="8dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:layout_behavior="@string/ScrollingAppBarLayoutBehavior"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="?android:colorBackground">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gallery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/gallery_height"
|
||||
android:background="?android:colorBackground"
|
||||
android:fitsSystemWindows="true"
|
||||
app:data="@{vm.charger.photos}"
|
||||
app:invisibleUnless="@{vm.charger.photos != null && vm.charger.photos.size() > 0}"
|
||||
app:layout_behavior="@string/BackDropBottomSheetBehavior" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_locate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_location"
|
||||
app:backgroundTint="@android:color/white"
|
||||
app:borderWidth="0dp"
|
||||
app:isFabActive="@{ vm.myLocationEnabled }"
|
||||
app:layout_behavior=".ui.HideOnScrollFabBehavior" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:fillViewport="true"
|
||||
android:orientation="vertical"
|
||||
app:anchorPoint="@dimen/gallery_height"
|
||||
app:behavior_hideable="true"
|
||||
app:behavior_peekHeight="@dimen/peek_height"
|
||||
app:defaultState="stateHidden"
|
||||
app:layout_behavior="@string/BottomSheetBehaviorGoogleMapsLike"
|
||||
tools:defaultState="stateCollapsed">
|
||||
|
||||
<include
|
||||
android:id="@+id/detail_view"
|
||||
layout="@layout/detail_view"
|
||||
app:charger="@{vm.charger}"
|
||||
app:availability="@{vm.availability}" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_directions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_directions"
|
||||
app:layout_anchor="@id/bottom_sheet"
|
||||
app:layout_anchorGravity="top|right|end"
|
||||
app:layout_behavior="@string/ScrollAwareFABBehavior" />
|
||||
|
||||
<!--<com.mahc.custombottomsheetbehavior.MergedAppBarLayout
|
||||
android:id="@+id/mergedappbarlayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/MergedAppBarLayoutBehavior"/>-->
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true" />
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
121
app/src/main/res/layout/fragment_map.xml
Normal file
121
app/src/main/res/layout/fragment_map.xml
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.johan.evmap.fragment.MapViewModel" />
|
||||
|
||||
<variable
|
||||
name="vm"
|
||||
type="MapViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/map"
|
||||
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".MapsActivity" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:elevation="8dp"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:layout_behavior="@string/ScrollingAppBarLayoutBehavior"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:backgroundTint="?android:colorBackground">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/search"
|
||||
android:inputType="text" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gallery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/gallery_height"
|
||||
android:background="?android:colorBackground"
|
||||
android:fitsSystemWindows="true"
|
||||
app:data="@{vm.charger.photos}"
|
||||
app:invisibleUnless="@{vm.charger.photos != null && vm.charger.photos.size() > 0}"
|
||||
app:layout_behavior="@string/BackDropBottomSheetBehavior" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_locate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_location"
|
||||
app:backgroundTint="@android:color/white"
|
||||
app:borderWidth="0dp"
|
||||
app:isFabActive="@{ vm.myLocationEnabled }"
|
||||
app:layout_behavior=".ui.HideOnScrollFabBehavior" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:fillViewport="true"
|
||||
android:orientation="vertical"
|
||||
app:anchorPoint="@dimen/gallery_height"
|
||||
app:behavior_hideable="true"
|
||||
app:behavior_peekHeight="@dimen/peek_height"
|
||||
app:defaultState="stateHidden"
|
||||
app:layout_behavior="@string/BottomSheetBehaviorGoogleMapsLike"
|
||||
tools:defaultState="stateCollapsed">
|
||||
|
||||
<include
|
||||
android:id="@+id/detail_view"
|
||||
layout="@layout/detail_view"
|
||||
app:charger="@{vm.charger}"
|
||||
app:availability="@{vm.availability}" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_directions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_directions"
|
||||
app:layout_anchor="@id/bottom_sheet"
|
||||
app:layout_anchorGravity="top|right|end"
|
||||
app:layout_behavior="@string/ScrollAwareFABBehavior" />
|
||||
|
||||
<!--<com.mahc.custombottomsheetbehavior.MergedAppBarLayout
|
||||
android:id="@+id/mergedappbarlayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/MergedAppBarLayoutBehavior"/>-->
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
19
app/src/main/res/navigation/nav_graph.xml
Normal file
19
app/src/main/res/navigation/nav_graph.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/mapFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/mapFragment"
|
||||
android:name="com.johan.evmap.fragment.MapFragment"
|
||||
android:label="MapFragment">
|
||||
<action
|
||||
android:id="@+id/action_mapFragment_to_galleryActivity"
|
||||
app:destination="@id/galleryActivity" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/galleryActivity"
|
||||
android:name="com.johan.evmap.GalleryActivity"
|
||||
android:label="GalleryActivity" />
|
||||
</navigation>
|
||||
@@ -21,4 +21,5 @@
|
||||
<string name="realtime_data_unavailable">Echtzeitstatus nicht verfügbar</string>
|
||||
<string name="realtime_data_source">Quelle Echtzeitdaten (beta): %s</string>
|
||||
<string name="go_to_goingelectric">Quelle: goingelectric.de</string>
|
||||
<string name="search">Suche</string>
|
||||
</resources>
|
||||
@@ -20,4 +20,5 @@
|
||||
<string name="realtime_data_unavailable">Real-time status unavailable</string>
|
||||
<string name="realtime_data_source">Real-time status source (beta): %s</string>
|
||||
<string name="go_to_goingelectric">Source: goingelectric.de</string>
|
||||
<string name="search">Search</string>
|
||||
</resources>
|
||||
|
||||
@@ -10,6 +10,9 @@ buildscript {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0-beta03'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
def nav_version = "2.3.0-alpha04"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user