Compare commits

..

1 Commits

Author SHA1 Message Date
johan12345
10ce6a0b5f AA/AAOS: implement TabTemplate for detail view 2023-09-02 22:21:07 +02:00
12 changed files with 98 additions and 94 deletions

View File

@@ -32,7 +32,6 @@ jobs:
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
FRONYX_API_KEY: ${{ secrets.FRONYX_API_KEY }}
ACRA_CRASHREPORT_CREDENTIALS: ${{ secrets.ACRA_CRASHREPORT_CREDENTIALS }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.KEYSTORE_ALIAS_PASSWORD }}

View File

@@ -19,8 +19,8 @@ android {
minSdkVersion 21
targetSdkVersion 34
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode 200
versionName "1.6.9"
versionCode 194
versionName "1.6.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs supportedLocales.split(',')
@@ -253,6 +253,7 @@ dependencies {
// ACRA (crash reporting)
def acraVersion = "5.11.1"
implementation("ch.acra:acra-mail:$acraVersion")
implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-dialog:$acraVersion")
implementation("ch.acra:acra-limiter:$acraVersion")

View File

@@ -11,6 +11,7 @@ import net.vonforst.evmap.ui.updateNightMode
import org.acra.config.dialog
import org.acra.config.httpSender
import org.acra.config.limiter
import org.acra.config.mailSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
@@ -36,14 +37,21 @@ class EvMapApplication : Application(), Configuration.Provider {
initAcra {
buildConfigClass = BuildConfig::class.java
// Vehicles often don't have an email app, so use HTTP to send instead
reportFormat = StringFormat.JSON
httpSender {
uri = getString(R.string.acra_backend_url)
val creds = getString(R.string.acra_credentials).split(":")
basicAuthLogin = creds[0]
basicAuthPassword = creds[1]
httpMethod = HttpSender.Method.POST
if (BuildConfig.FLAVOR_automotive == "automotive") {
// Vehicles often don't have an email app, so use HTTP to send instead
httpSender {
uri = getString(R.string.acra_backend_url)
val creds = getString(R.string.acra_credentials).split(":")
basicAuthLogin = creds[0]
basicAuthPassword = creds[1]
httpMethod = HttpSender.Method.POST
}
} else {
mailSender {
mailTo = "evmap+crashreport@vonforst.net"
}
}
dialog {

View File

@@ -105,19 +105,20 @@ class EnBwAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
var markers =
api.getMarkers(lng - coordRange, lng + coordRange, lat - coordRange, lat + coordRange)
markers = markers.flatMap {
if (it.grouped) {
api.getMarkers(
it.viewPort.lowerLeftLon,
it.viewPort.upperRightLon,
it.viewPort.lowerLeftLat,
it.viewPort.upperRightLat
)
} else {
listOf(it)
while (markers.any { it.grouped }) {
markers = markers.flatMap {
if (it.grouped) {
api.getMarkers(
it.viewPort.lowerLeftLon,
it.viewPort.upperRightLon,
it.viewPort.lowerLeftLat,
it.viewPort.upperRightLat
)
} else {
listOf(it)
}
}
}
if (markers.any { it.grouped }) throw AvailabilityDetectorException("markers still grouped")
val nearest = markers.minByOrNull { marker ->
distanceBetween(marker.lat, marker.lon, lat, lng)

View File

@@ -10,8 +10,10 @@ import android.text.Spanned
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.annotations.ExperimentalCarApi
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.*
import androidx.car.app.model.TabTemplate.TabCallback
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.scale
import androidx.core.text.HtmlCompat
@@ -48,7 +50,10 @@ import java.time.format.FormatStyle
import kotlin.math.roundToInt
@ExperimentalCarApi
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
private val TAB_MAIN = "main"
var charger: ChargeLocation? = null
var photo: Bitmap? = null
private var availability: ChargeLocationStatus? = null
@@ -68,6 +73,8 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
private val maxRows = ctx.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_PANE)
private val largeImageSupported =
ctx.carAppApiLevel >= 4 // since API 4, Row.setImage is supported
private val tabsSupported = ctx.carAppApiLevel >= 6
private var currentTab = TAB_MAIN
private var favorite: Favorite? = null
private var favoriteUpdateJob: Job? = null
@@ -79,6 +86,45 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
override fun onGetTemplate(): Template {
if (charger == null) loadCharger()
if (tabsSupported) {
return generateTabs()
} else {
return generateMainPane()
}
}
private fun generateTabs(): TabTemplate {
return TabTemplate.Builder(object : TabCallback {
override fun onTabSelected(tabContentId: String) {
currentTab = tabContentId
invalidate()
}
}).apply {
charger?.let {
addTab(
Tab.Builder()
.setTitle(carContext.getString(R.string.general_info))
.setIcon(CarIcon.APP_ICON)
.setContentId(TAB_MAIN).build()
)
addTab(
Tab.Builder()
.setTitle("bla")
.setIcon(CarIcon.APP_ICON)
.setContentId("bla").build()
)
val contents = when (currentTab) {
TAB_MAIN -> generateMainPane()
else -> throw IllegalArgumentException("invalid tab")
}
setTabContents(TabContents.Builder(contents).build())
setActiveTabContentId(currentTab)
} ?: setLoading(true)
setHeaderAction(Action.APP_ICON)
}.build()
}
private fun generateMainPane(): PaneTemplate {
return PaneTemplate.Builder(
Pane.Builder().apply {
charger?.let { charger ->
@@ -100,28 +146,28 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
)
.setTitle(carContext.getString(R.string.navigate))
.setFlags(Action.FLAG_PRIMARY)
.setBackgroundColor(CarColor.PRIMARY)
.setOnClickListener {
navigateToCharger(charger)
}
.build())
if (ChargepriceApi.isChargerSupported(charger)) {
addAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_chargeprice
)
).build()
)
.setTitle(carContext.getString(R.string.auto_prices))
.setBackgroundColor(CarColor.PRIMARY)
.setOnClickListener {
navigateToCharger(charger)
}
.build())
if (ChargepriceApi.isChargerSupported(charger)) {
addAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_chargeprice
)
).build()
)
.setTitle(carContext.getString(R.string.auto_prices))
.setOnClickListener {
screenManager.push(ChargepriceScreen(carContext, charger))
}
.build())
}
}
} ?: setLoading(true)
}.build()
).apply {
@@ -290,18 +336,6 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
}.build())
}
}
if (rows.count() < maxRows && charger.generalInformation != null) {
rows.add(Row.Builder().apply {
setTitle(carContext.getString(R.string.general_info))
addText(charger.generalInformation)
}.build())
}
if (rows.count() < maxRows && charger.amenities != null) {
rows.add(Row.Builder().apply {
setTitle(carContext.getString(R.string.amenities))
addText(charger.amenities)
}.build())
}
return rows
}
@@ -383,10 +417,8 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
} else {
append(nameForPlugType(carContext.stringProvider(), cp.type))
}
cp.formatPower()?.let {
append(" ")
append(it)
}
append(" ")
append(cp.formatPower())
}
availability?.status?.get(cp)?.let { status ->
chargepointsText.append(

View File

@@ -41,9 +41,7 @@ class FilterScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
if (filterStatus in listOf(FILTERS_DISABLED, FILTERS_FAVORITES, FILTERS_CUSTOM)) {
page = 0
} else {
val index =
paginateProfiles(it).indexOfFirst { it.any { it.id == filterStatus } }
page = index.takeUnless { it == -1 } ?: 0
page = paginateProfiles(it).indexOfFirst { it.any { it.id == filterStatus } }
}
invalidate()
}

View File

@@ -1043,7 +1043,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
binding.search.requestFocus()
binding.search.setSelection(locationName.length)
}
if (context.checkAnyLocationPermission() && prefs.currentMapMyLocationEnabled) {
if (context.checkAnyLocationPermission()) {
enableLocation(!positionSet, false)
positionSet = true
}
@@ -1400,9 +1400,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
prefs.currentMapLocation = it.bounds.center
prefs.currentMapZoom = it.zoom
}
vm.myLocationEnabled.value?.let {
prefs.currentMapMyLocationEnabled = it
}
}
override fun onDestroy() {

View File

@@ -291,12 +291,6 @@ class PreferenceDataSource(val context: Context) {
sp.edit().putFloat("current_map_zoom", value).apply()
}
var currentMapMyLocationEnabled: Boolean
get() = sp.getBoolean("current_map_my_location_enabled", false)
set(value) {
sp.edit().putBoolean("current_map_my_location_enabled", value).apply()
}
var privacyAccepted: Boolean
get() = sp.getBoolean("privacy_accepted", false)
set(value) {

View File

@@ -1,8 +0,0 @@
Verbesserungen:
- Neue Einstellung für Maßeinheiten
- Anpassungen für Android 14
- Android Auto: Weitere Detailbeschreibungen zu den Ladestationen
- Android Auto: Löschbutton in der Filterliste
Fehler behoben:
- Fehler beim Laden der EnBW Echtzeitdaten

View File

@@ -1,5 +0,0 @@
Verbesserungen:
- Beim Start der App wird nun der zuletzt gesehene Kartenausschnitt gezeigt
Fehler behoben:
- Abstürze behoben

View File

@@ -1,8 +0,0 @@
Improvements:
- New setting for units of measurement
- Adjustments for Android 14
- Android Auto: More detailed descriptions of chargers
- Android Auto: Delete button in filter list
Bugfixes:
- Errors loading realtime data from EnBW

View File

@@ -1,5 +0,0 @@
Improvements:
- When starting the app, the last viewed map area will be shown
Bugfixes:
- Fixed crashes