Add some stuff in detail view

This commit is contained in:
Johan von Forstner
2020-03-23 20:42:08 +01:00
parent 1645d7c1a4
commit d5fdbe71e5
23 changed files with 452 additions and 68 deletions

View File

@@ -2,10 +2,12 @@ package com.johan.evmap
import android.app.Application
import com.facebook.stetho.Stetho
import com.jakewharton.threetenabp.AndroidThreeTen
class EvMapApplication : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this);
Stetho.initializeWithDefaults(this);
}
}

View File

@@ -1,7 +1,9 @@
package com.johan.evmap
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
@@ -20,8 +22,10 @@ import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.material.snackbar.Snackbar
import com.google.maps.android.ui.IconGenerator
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.ActivityMapsBinding
@@ -51,21 +55,9 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
mapFragment.getMapAsync(this)
api = GoingElectricApi.create(getString(R.string.goingelectric_key))
setupAdapters()
val behavior = BottomSheetBehaviorGoogleMapsLike.from(binding.bottomSheet)
binding.gallery.adapter = GalleryAdapter(this)
binding.gallery.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.gallery.addItemDecoration(DividerItemDecoration(
this, LinearLayoutManager.HORIZONTAL
).apply {
setDrawable(getDrawable(R.drawable.gallery_divider)!!)
})
binding.detailView.connectors.adapter = ConnectorAdapter()
binding.detailView.connectors.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
binding.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
var previousCharger = binding.charger
@@ -100,7 +92,62 @@ class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
enableLocation(true)
}
}
binding.fabDirections.setOnClickListener {
val charger = binding.charger
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
)
}
}
}
}
}
private fun setupAdapters() {
binding.gallery.apply {
adapter = GalleryAdapter(this@MapsActivity)
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
)
)
}
}

View File

@@ -1,5 +1,6 @@
package com.johan.evmap.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
@@ -9,6 +10,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.johan.evmap.BR
import com.johan.evmap.R
import com.johan.evmap.api.ChargeLocation
import com.johan.evmap.api.Chargepoint
interface Equatable {
@@ -46,4 +48,35 @@ abstract class DataBindingAdapter<T : Equatable>() :
class ConnectorAdapter : DataBindingAdapter<Chargepoint>() {
override fun getItemViewType(position: Int): Int = R.layout.item_connector
}
}
class DetailAdapter : DataBindingAdapter<DetailAdapter.Detail>() {
data class Detail(val icon: Int, val contentDescription: Int, val text: CharSequence) :
Equatable
override fun getItemViewType(position: Int): Int = R.layout.item_detail
}
fun buildDetails(loc: ChargeLocation?, ctx: Context): List<DetailAdapter.Detail> {
if (loc == null) return emptyList()
return listOfNotNull(
DetailAdapter.Detail(R.drawable.ic_address, R.string.address, loc.address.toString()),
if (loc.operator != null) DetailAdapter.Detail(
R.drawable.ic_operator,
R.string.operator,
loc.operator
) else null,
if (loc.network != null) DetailAdapter.Detail(
R.drawable.ic_network,
R.string.network,
loc.network
) else null,
// TODO: separate layout for opening hours with expandable details
if (loc.openinghours != null) DetailAdapter.Detail(
R.drawable.ic_hours,
R.string.hours,
loc.openinghours.getStatusText(ctx)
) else null
)
}

View File

@@ -1,6 +1,7 @@
package com.johan.evmap.api
import com.squareup.moshi.*
import org.threeten.bp.LocalTime
import java.lang.reflect.Type
@@ -101,3 +102,31 @@ private fun hasJsonObjectOrFalseAnnotation(annotations: Set<Annotation>?) =
annotation class JsonObjectOrFalse {
}
internal class HoursAdapter {
private val regex = Regex("from (.*) till (.*)")
@FromJson
fun fromJson(str: String): Hours? {
if (str == "closed") {
return Hours(null, null)
} else {
val match = regex.find(str)
?: throw IllegalArgumentException("$str does not match hours format")
return Hours(
LocalTime.parse(match.groupValues[1]),
LocalTime.parse(match.groupValues[2])
)
}
}
@ToJson
fun toJson(value: Hours): String {
if (value.start == null || value.end == null) {
return "closed"
} else {
return "from ${value.start} till ${value.end}"
}
}
}

View File

@@ -38,6 +38,7 @@ interface GoingElectricApi {
val moshi = Moshi.Builder()
.add(ChargepointListItemJsonAdapterFactory())
.add(JsonObjectOrFalseAdapter.Factory())
.add(HoursAdapter())
.build()
val retrofit = Retrofit.Builder()

View File

@@ -1,8 +1,14 @@
package com.johan.evmap.api
import android.content.Context
import androidx.core.text.HtmlCompat
import com.johan.evmap.R
import com.johan.evmap.adapter.Equatable
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalTime
@JsonClass(generateAdapter = true)
data class ChargepointList(
@@ -26,8 +32,9 @@ data class ChargeLocation(
// only shown in details:
@JsonObjectOrFalse val operator: String?,
@Json(name = "general_information") @JsonObjectOrFalse val generalInformation: String?,
val photos: List<ChargerPhoto>?
val photos: List<ChargerPhoto>?,
//val chargecards: Boolean?
val openinghours: OpeningHours?
) : ChargepointListItem() {
val maxPower: Double
get() {
@@ -41,6 +48,79 @@ data class ChargeLocation(
}
}
@JsonClass(generateAdapter = true)
data class OpeningHours(
@Json(name = "24/7") val twentyfourSeven: Boolean,
@JsonObjectOrFalse val description: String?,
val days: OpeningHoursDays?
) {
fun getStatusText(ctx: Context): CharSequence {
if (twentyfourSeven) {
return HtmlCompat.fromHtml(ctx.getString(R.string.open_247), 0)
} else if (days != null) {
val hours = days.getHoursForDate(LocalDate.now())
if (hours.start == null || hours.end == null) {
return HtmlCompat.fromHtml(ctx.getString(R.string.closed), 0)
}
val now = LocalTime.now()
if (hours.start.isBefore(now) && hours.end.isAfter(now)) {
return HtmlCompat.fromHtml(
ctx.getString(
R.string.open_closesat,
hours.end.toString()
), 0
)
} else if (hours.end.isBefore(now)) {
return HtmlCompat.fromHtml(ctx.getString(R.string.closed), 0)
} else {
return HtmlCompat.fromHtml(
ctx.getString(
R.string.closed_opensat,
hours.start.toString()
), 0
)
}
} else if (description != null) {
return description
} else {
return ""
}
}
}
@JsonClass(generateAdapter = true)
data class OpeningHoursDays(
val monday: Hours,
val tuesday: Hours,
val wednesday: Hours,
val thursday: Hours,
val friday: Hours,
val saturday: Hours,
val sunday: Hours,
val holiday: Hours
) {
fun getHoursForDate(date: LocalDate): Hours {
// TODO: check for holidays
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
return when (date.dayOfWeek) {
DayOfWeek.MONDAY -> monday
DayOfWeek.TUESDAY -> tuesday
DayOfWeek.WEDNESDAY -> wednesday
DayOfWeek.THURSDAY -> thursday
DayOfWeek.FRIDAY -> friday
DayOfWeek.SATURDAY -> saturday
DayOfWeek.SUNDAY -> sunday
}
}
}
data class Hours(
val start: LocalTime?,
val end: LocalTime?
)
@JsonClass(generateAdapter = true)
data class ChargerPhoto(val id: String)

View File

@@ -49,4 +49,14 @@ fun getConnectorItem(view: ImageView, type: String) {
else -> 0
}
)
}
@BindingAdapter("srcCompat")
fun setImageResource(imageView: ImageView, resource: Int) {
imageView.setImageResource(resource)
}
@BindingAdapter("android:contentDescription")
fun setContentDescriptionResource(imageView: ImageView, resource: Int) {
imageView.contentDescription = imageView.context.getString(resource)
}