diff --git a/app/build.gradle b/app/build.gradle index 66593f25..be2685a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,6 +15,7 @@ android { applicationId "com.openvehicles.OVMS" minSdk 21 targetSdk 36 + vectorDrawables.useSupportLibrary = true } buildTypes { release { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index db0dc0ed..3f3d420a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,6 +48,7 @@ @@ -72,6 +73,7 @@ 23) packStatus.voltRef = result[23].toFloat() - if (result.size > 26) packStatus.current = result[26].toFloat() - packStatus.tempAmbient = result[17].toFloat() - if (result.size > 25) packStatus.tempCharger = result[25].toFloat() - doors = result[18].toInt() // doors3 + packStatus.volt = result[21 + shift].toFloat() + if (result.size > 23 + shift) packStatus.voltRef = result[23 + shift].toFloat() + if (result.size > 26 + shift) packStatus.current = result[26 + shift].toFloat() + packStatus.tempAmbient = result[17 + shift].toFloat() + if (result.size > 25 + shift) packStatus.tempCharger = result[25 + shift].toFloat() + doors = result[18 + shift].toInt() // doors3 packStatus.isCarAwake = doors and 0x01 != 0 - if (result.size > 24) { - doors = result[24].toInt() // doors5 + if (result.size > 24 + shift) { + doors = result[24 + shift].toInt() // doors5 packStatus.isCharging12V = doors and 0x10 != 0 } @@ -125,7 +142,7 @@ class AuxBatteryData { Log.v(TAG, "loading from file: $filename") return try { inputStream = context!!.openFileInput(filename) - val isr = InputStreamReader(inputStream) + val isr = InputStreamReader(inputStream, StandardCharsets.UTF_8) val bufferedReader = BufferedReader(isr) val sb = StringBuilder() var line: String? diff --git a/app/src/main/java/com/openvehicles/OVMS/entities/BatteryData.kt b/app/src/main/java/com/openvehicles/OVMS/entities/BatteryData.kt index e1c382ae..d191a0e0 100644 --- a/app/src/main/java/com/openvehicles/OVMS/entities/BatteryData.kt +++ b/app/src/main/java/com/openvehicles/OVMS/entities/BatteryData.kt @@ -8,8 +8,10 @@ import java.io.BufferedReader import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStreamReader +import java.nio.charset.StandardCharsets import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale import java.util.TimeZone /** @@ -32,7 +34,7 @@ class BatteryData { val json = gson.toJson(this) return try { outputStream = context!!.openFileOutput(filename, Context.MODE_PRIVATE) - outputStream.write(json.toByteArray()) + outputStream.write(json.toByteArray(StandardCharsets.UTF_8)) outputStream.close() true } catch (e: Exception) { @@ -46,7 +48,7 @@ class BatteryData { var recCnt: Int var recType: String var timeStamp: Date - val serverTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + val serverTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) serverTime.timeZone = TimeZone.getTimeZone("UTC") var packStatus: PackStatus? = null var cells: ArrayList? = null @@ -83,28 +85,43 @@ class BatteryData { recNr = result[2].toInt() recCnt = result[3].toInt() recType = result[4] - timeStamp = serverTime.parse(result[5]) + + // Robust timestamp parsing: + var shift = 0 + try { + timeStamp = serverTime.parse(result[5])!! + } catch (e: Exception) { + // try combined with next part if next part looks like time: + try { + timeStamp = serverTime.parse(result[5].trim() + " " + result[6].trim())!! + shift = 1 + } catch (e2: Exception) { + Log.w(TAG, "failed to parse timestamp: " + result[5]) + continue + } + } + Log.v(TAG, "processing recType $recType entryNr $recNr/$recCnt") if (recType == "RT-BAT-P") { try { // create record: packStatus = PackStatus() packStatus.timeStamp = timeStamp - packStatus.voltAlert = result[7].toInt() - packStatus.tempAlert = result[8].toInt() - packStatus.soc = result[9].toInt().toFloat() / 100 - packStatus.socMin = result[10].toInt().toFloat() / 100 - packStatus.socMax = result[11].toInt().toFloat() / 100 - packStatus.volt = result[12].toInt().toFloat() / 10 - packStatus.voltMin = result[13].toInt().toFloat() / 10 - packStatus.voltMax = result[14].toInt().toFloat() / 10 - packStatus.temp = result[15].toFloat() - packStatus.tempMin = result[16].toFloat() - packStatus.tempMax = result[17].toFloat() - packStatus.voltDevMax = result[18].toInt().toFloat() / 100 - packStatus.tempDevMax = result[19].toFloat() - packStatus.maxDrivePwr = (result[20].toInt() * 100).toLong() - packStatus.maxRecupPwr = (result[21].toInt() * 100).toLong() + packStatus.voltAlert = result[7 + shift].toInt() + packStatus.tempAlert = result[8 + shift].toInt() + packStatus.soc = result[9 + shift].toInt().toFloat() / 100 + packStatus.socMin = result[10 + shift].toInt().toFloat() / 100 + packStatus.socMax = result[11 + shift].toInt().toFloat() / 100 + packStatus.volt = result[12 + shift].toInt().toFloat() / 10 + packStatus.voltMin = result[13 + shift].toInt().toFloat() / 10 + packStatus.voltMax = result[14 + shift].toInt().toFloat() / 10 + packStatus.temp = result[15 + shift].toFloat() + packStatus.tempMin = result[16 + shift].toFloat() + packStatus.tempMax = result[17 + shift].toFloat() + packStatus.voltDevMax = result[18 + shift].toInt().toFloat() / 100 + packStatus.tempDevMax = result[19 + shift].toFloat() + packStatus.maxDrivePwr = (result[20 + shift].toInt() * 100).toLong() + packStatus.maxRecupPwr = (result[21 + shift].toInt() * 100).toLong() // store record: if (packStatus.volt > 0) packHistory.add(packStatus) @@ -114,7 +131,7 @@ class BatteryData { } } else if (recType == "RT-BAT-C") { try { - nrCell = result[6].toInt() + nrCell = result[6 + shift].toInt() if (nrCell > cellCount) cellCount = nrCell // Pack record(s) complete? @@ -136,16 +153,16 @@ class BatteryData { // create new record: cellStatus = CellStatus() - cellStatus.voltAlert = result[7].toInt() - cellStatus.tempAlert = result[8].toInt() - cellStatus.volt = result[9].toInt().toFloat() / 1000 - cellStatus.voltMin = result[10].toInt().toFloat() / 1000 - cellStatus.voltMax = result[11].toInt().toFloat() / 1000 - cellStatus.voltDevMax = result[12].toInt().toFloat() / 1000 - cellStatus.temp = result[13].toFloat() - cellStatus.tempMin = result[14].toFloat() - cellStatus.tempMax = result[15].toFloat() - cellStatus.tempDevMax = result[16].toFloat() + cellStatus.voltAlert = result[7 + shift].toInt() + cellStatus.tempAlert = result[8 + shift].toInt() + cellStatus.volt = result[9 + shift].toInt().toFloat() / 1000 + cellStatus.voltMin = result[10 + shift].toInt().toFloat() / 1000 + cellStatus.voltMax = result[11 + shift].toInt().toFloat() / 1000 + cellStatus.voltDevMax = result[12 + shift].toInt().toFloat() / 1000 + cellStatus.temp = result[13 + shift].toFloat() + cellStatus.tempMin = result[14 + shift].toFloat() + cellStatus.tempMax = result[15 + shift].toFloat() + cellStatus.tempDevMax = result[16 + shift].toFloat() // store record: cells!![nrCell - 1] = cellStatus @@ -201,7 +218,7 @@ class BatteryData { Log.v(TAG, "loading from file: $filename") return try { inputStream = context!!.openFileInput(filename) - val isr = InputStreamReader(inputStream) + val isr = InputStreamReader(inputStream, StandardCharsets.UTF_8) val bufferedReader = BufferedReader(isr) val sb = StringBuilder() var line: String? diff --git a/app/src/main/java/com/openvehicles/OVMS/entities/CarData.kt b/app/src/main/java/com/openvehicles/OVMS/entities/CarData.kt index aa603887..f46482d1 100644 --- a/app/src/main/java/com/openvehicles/OVMS/entities/CarData.kt +++ b/app/src/main/java/com/openvehicles/OVMS/entities/CarData.kt @@ -48,6 +48,10 @@ class CarData : Serializable { @JvmField var sel_vehicle_image = "" // The vehicle image (on disk) @JvmField + var sel_vehicle_image_ol = "" // The vehicle top-view image (on disk) + @JvmField + var sel_vehicle_image_map = "" // The vehicle map icon image (on disk) + @JvmField var car_lastupdated: Date? = null var server_carsconnected = 0 diff --git a/app/src/main/java/com/openvehicles/OVMS/entities/LogsData.kt b/app/src/main/java/com/openvehicles/OVMS/entities/LogsData.kt index ba02f65a..1186c5ca 100644 --- a/app/src/main/java/com/openvehicles/OVMS/entities/LogsData.kt +++ b/app/src/main/java/com/openvehicles/OVMS/entities/LogsData.kt @@ -9,9 +9,11 @@ import java.io.BufferedReader import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStreamReader +import java.nio.charset.StandardCharsets import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale import java.util.TimeZone /** @@ -71,7 +73,7 @@ class LogsData { val json = gson.toJson(this) return try { outputStream = context!!.openFileOutput(filename, Context.MODE_PRIVATE) - outputStream.write(json.toByteArray()) + outputStream.write(json.toByteArray(StandardCharsets.UTF_8)) outputStream.close() true } catch (e: Exception) { @@ -84,9 +86,8 @@ class LogsData { var recNr: Int var recCnt: Int var recType: String - var timeStamp: String var entryNr: Int - val serverTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + val serverTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) serverTime.timeZone = TimeZone.getTimeZone("UTC") for (i in 0 until cmdSeries.size()) { val cmd = cmdSeries[i] @@ -98,28 +99,40 @@ class LogsData { recNr = result[2].toInt() recCnt = result[3].toInt() recType = result[4] - timeStamp = result[5] - entryNr = result[6].toInt() + + // Robust timestamp parsing: + var shift = 0 + var ts: Date? = null + try { + ts = serverTime.parse(result[5])!! + } catch (e: Exception) { + // try combined with next part if next part looks like time: + try { + ts = serverTime.parse(result[5].trim() + " " + result[6].trim())!! + shift = 1 + } catch (e2: Exception) { + Log.w(TAG, "failed to parse timestamp: " + result[5]) + continue + } + } + + entryNr = result[6 + shift].toInt() // [7++] = Payload Log.v(TAG, "processing recType $recType entryNr $entryNr") if (recType == "RT-ENG-LogKeyTime") { keyTime = KeyTime() - keyTime.keyHour = result[7].toInt() - keyTime.keyMinSec = result[8].toInt() - try { - keyTime.timeStamp = serverTime.parse(timeStamp) - } catch (e: Exception) { - keyTime.timeStamp = Date() - } + keyTime.keyHour = result[7 + shift].toInt() + keyTime.keyMinSec = result[8 + shift].toInt() + keyTime.timeStamp = ts } else if (recType == "RT-ENG-LogAlerts") { if (entryNr == 0) { alerts = ArrayList(20) alertsTime = keyTime } val alert = Alert() - alert.code = result[7] - alert.description = result[8] + alert.code = result[7 + shift] + alert.description = result[8 + shift] alerts.add(alert) } else if (recType == "RT-ENG-LogFaults") { if (entryNr == 0) { @@ -127,15 +140,15 @@ class LogsData { faultEventsTime = keyTime } val event = Event() - event.code = result[7] - event.description = result[8] + event.code = result[7 + shift] + event.description = result[8 + shift] event.time = KeyTime() - event.time!!.keyHour = result[9].toInt() - event.time!!.keyMinSec = result[10].toInt() + event.time!!.keyHour = result[9 + shift].toInt() + event.time!!.keyMinSec = result[10 + shift].toInt() event.data = arrayOfNulls(3) - event.data[0] = result[11] - event.data[1] = result[12] - event.data[2] = result[13] + event.data[0] = result[11 + shift] + event.data[1] = result[12 + shift] + event.data[2] = result[13 + shift] faultEvents.add(event) } else if (recType == "RT-ENG-LogSystem") { if (entryNr == 0) { @@ -143,15 +156,15 @@ class LogsData { systemEventsTime = keyTime } val event = Event() - event.code = result[7] - event.description = result[8] + event.code = result[7 + shift] + event.description = result[8 + shift] event.time = KeyTime() - event.time!!.keyHour = result[9].toInt() - event.time!!.keyMinSec = result[10].toInt() + event.time!!.keyHour = result[9 + shift].toInt() + event.time!!.keyMinSec = result[10 + shift].toInt() event.data = arrayOfNulls(3) - event.data[0] = result[11] - event.data[1] = result[12] - event.data[2] = result[13] + event.data[0] = result[11 + shift] + event.data[1] = result[12 + shift] + event.data[2] = result[13 + shift] systemEvents.add(event) } else if (recType == "RT-ENG-LogCounts") { if (entryNr == 0) { @@ -159,15 +172,15 @@ class LogsData { countersTime = keyTime } val counter = Counter() - counter.code = result[7] - counter.description = result[8] + counter.code = result[7 + shift] + counter.description = result[8 + shift] counter.lastTime = KeyTime() - counter.lastTime!!.keyHour = result[9].toInt() - counter.lastTime!!.keyMinSec = result[10].toInt() + counter.lastTime!!.keyHour = result[9 + shift].toInt() + counter.lastTime!!.keyMinSec = result[10 + shift].toInt() counter.firstTime = KeyTime() - counter.firstTime!!.keyHour = result[11].toInt() - counter.firstTime!!.keyMinSec = result[12].toInt() - counter.count = result[13].toInt() + counter.firstTime!!.keyHour = result[11 + shift].toInt() + counter.firstTime!!.keyMinSec = result[12 + shift].toInt() + counter.count = result[13 + shift].toInt() counters.add(counter) } else if (recType == "RT-ENG-LogMinMax") { if (entryNr == 0) { @@ -175,16 +188,16 @@ class LogsData { minMaxesTime = keyTime } val minMax = MinMax() - minMax.batteryVoltageMin = result[7].toDouble() / 16 - minMax.batteryVoltageMax = result[8].toDouble() / 16 - minMax.capacitorVoltageMin = result[9].toDouble() / 16 - minMax.capacitorVoltageMax = result[10].toDouble() / 16 - minMax.motorCurrentMin = result[11].toInt() - minMax.motorCurrentMax = result[12].toInt() - minMax.motorSpeedMin = result[13].toInt() - minMax.motorSpeedMax = result[14].toInt() - minMax.deviceTempMin = result[15].toInt() - minMax.deviceTempMax = result[16].toInt() + minMax.batteryVoltageMin = result[7 + shift].toDouble() / 16 + minMax.batteryVoltageMax = result[8 + shift].toDouble() / 16 + minMax.capacitorVoltageMin = result[9 + shift].toDouble() / 16 + minMax.capacitorVoltageMax = result[10 + shift].toDouble() / 16 + minMax.motorCurrentMin = result[11 + shift].toInt() + minMax.motorCurrentMax = result[12 + shift].toInt() + minMax.motorSpeedMin = result[13 + shift].toInt() + minMax.motorSpeedMax = result[14 + shift].toInt() + minMax.deviceTempMin = result[15 + shift].toInt() + minMax.deviceTempMax = result[16 + shift].toInt() minMaxes.add(minMax) } } catch (e: Exception) { @@ -220,7 +233,7 @@ class LogsData { Log.v(TAG, "loading from file: $filename") return try { inputStream = context!!.openFileInput(filename) - val isr = InputStreamReader(inputStream) + val isr = InputStreamReader(inputStream, StandardCharsets.UTF_8) val bufferedReader = BufferedReader(isr) val sb = StringBuilder() var line: String? diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/ApiActivity.kt b/app/src/main/java/com/openvehicles/OVMS/ui/ApiActivity.kt index 7301f99e..769a9d54 100755 --- a/app/src/main/java/com/openvehicles/OVMS/ui/ApiActivity.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/ApiActivity.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.os.IBinder import android.util.Log import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import com.openvehicles.OVMS.api.ApiObservable.addObserver import com.openvehicles.OVMS.api.ApiObservable.deleteObserver import com.openvehicles.OVMS.api.ApiObservable.notifyOnBind diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/AuxBatteryFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/AuxBatteryFragment.kt index 36281d71..b1624f71 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/AuxBatteryFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/AuxBatteryFragment.kt @@ -1,5 +1,6 @@ package com.openvehicles.OVMS.ui +import android.content.res.Configuration import android.graphics.Color import android.graphics.Paint import android.os.Bundle @@ -14,6 +15,8 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.components.LimitLine import com.github.mikephil.charting.components.XAxis @@ -25,6 +28,7 @@ import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.google.android.material.color.MaterialColors import com.openvehicles.OVMS.R import com.openvehicles.OVMS.entities.AuxBatteryData import com.openvehicles.OVMS.entities.AuxBatteryData.Companion.loadFile @@ -32,6 +36,7 @@ import com.openvehicles.OVMS.entities.CarData import com.openvehicles.OVMS.entities.CmdSeries import com.openvehicles.OVMS.ui.utils.ProgressOverlay import com.openvehicles.OVMS.ui.utils.TimeAxisValueFormatter +import com.openvehicles.OVMS.ui.utils.Ui import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.utils.CarsStorage.getSelectedCarData import java.text.SimpleDateFormat @@ -65,6 +70,15 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O private var carData: CarData? = null private var cmdSeries: CmdSeries? = null private var appPrefs: AppPrefs? = null + + private var colorText = Color.WHITE + private var colorGrid = Color.GRAY + private var colorVolt = COLOR_VOLT + private var colorVoltGrid = COLOR_VOLT_GRID + private var colorTempAmbient = COLOR_TEMP_AMBIENT + private var colorTempCharger = COLOR_TEMP_CHARGER + private var colorCurrText = COLOR_CURR_TEXT + private var colorCurrGrid = COLOR_CURR_GRID /** * Check data model validity @@ -89,6 +103,21 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O showTemp = true } + // Init theme colors: + val ctx = requireContext() + colorText = MaterialColors.getColor(ctx, android.R.attr.textColorPrimary, Color.WHITE) + colorGrid = ColorUtils.setAlphaComponent(colorText, 40) + + val isNight = (ctx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + if (!isNight) { + colorVolt = Color.parseColor("#669900") + colorVoltGrid = ColorUtils.setAlphaComponent(colorVolt, 100) + colorTempAmbient = Color.parseColor("#998800") + colorTempCharger = Color.parseColor("#996600") + colorCurrText = Color.parseColor("#994444") + colorCurrGrid = ColorUtils.setAlphaComponent(colorCurrText, 100) + } + // Setup UI: val rootView = inflater.inflate(R.layout.fragment_auxbattery, null) val progressOverlay = createProgressOverlay(rootView, false) @@ -97,6 +126,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O // Setup Pack history chart: // packChart = rootView.findViewById(R.id.auxbattery_chart_pack) as LineChart + packChart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) packChart.description.isEnabled = false packChart.setPinchZoom(false) // = pinch zoom in one direction at a time packChart.setDrawGridBackground(false) @@ -115,16 +145,16 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O } }) xAxis = packChart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.isGranularityEnabled = true xAxis.setGranularity(60f) // one minute yAxisLeft = packChart.axisLeft - yAxisLeft.textColor = COLOR_CURR_TEXT - yAxisLeft.gridColor = COLOR_CURR_GRID + yAxisLeft.textColor = colorCurrText + yAxisLeft.gridColor = colorCurrGrid yAxisLeft.valueFormatter = DefaultAxisValueFormatter(0) yAxisRight = packChart.axisRight - yAxisRight.textColor = COLOR_VOLT - yAxisRight.gridColor = COLOR_VOLT_GRID + yAxisRight.textColor = colorVolt + yAxisRight.gridColor = colorVoltGrid yAxisRight.valueFormatter = DefaultAxisValueFormatter(1) yAxisRight.setGranularity(0.1f) @@ -343,7 +373,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O if (packStatus.isNewSection(lastStatus)) { val l = LimitLine(xpos) l.label = timeFmt.format(packStatus.timeStamp!!) - l.textColor = Color.WHITE + l.textColor = colorText l.textStyle = Paint.Style.FILL l.setTextSize(6f) l.enableDashedLine(3f, 2f, 0f) @@ -368,7 +398,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O ) packTempAmbientSet = dataSet dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.setColor(COLOR_TEMP_AMBIENT) + dataSet.setColor(colorTempAmbient) dataSet.setLineWidth(3f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -378,7 +408,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O LineDataSet(tempChargerValues, getString(R.string.aux_battery_data_temp_charger)) packTempChargerSet = dataSet dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.setColor(COLOR_TEMP_CHARGER) + dataSet.setColor(colorTempCharger) dataSet.setLineWidth(3f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -404,7 +434,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O dataSet = LineDataSet(voltValues, getString(R.string.aux_battery_data_volt)) packVoltSet = dataSet dataSet.axisDependency = YAxis.AxisDependency.RIGHT - dataSet.setColor(COLOR_VOLT) + dataSet.setColor(colorVolt) dataSet.setLineWidth(3f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -455,7 +485,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O // display data sets: val data = LineData(dataSets) packData = data - data.setValueTextColor(Color.WHITE) + data.setValueTextColor(colorText) data.setValueTextSize(9f) packChart.setData(data) val xAxis = packChart.xAxis @@ -469,7 +499,7 @@ class AuxBatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.O } xAxis.addLimitLine(l) } - packChart.legend.textColor = Color.WHITE + packChart.legend.textColor = colorText packChart.invalidate() } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/BaseFragmentActivity.kt b/app/src/main/java/com/openvehicles/OVMS/ui/BaseFragmentActivity.kt index a701206c..ef99e158 100755 --- a/app/src/main/java/com/openvehicles/OVMS/ui/BaseFragmentActivity.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/BaseFragmentActivity.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.res.Configuration.ORIENTATION_UNDEFINED import android.os.Bundle import android.widget.FrameLayout +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_FADE import com.openvehicles.OVMS.R @@ -19,6 +20,7 @@ class BaseFragmentActivity : ApiActivity() { get() = supportFragmentManager.findFragmentById(contentId) public override fun onCreate(savedInstanceState: Bundle?) { + WindowCompat.setDecorFitsSystemWindows(window, true) super.onCreate(savedInstanceState) if (intent == null) { @@ -51,6 +53,7 @@ class BaseFragmentActivity : ApiActivity() { private fun setDefaultContentView() { val fl = FrameLayout(this) fl.setId(R.id.content) + fl.fitsSystemWindows = true setContentView(fl) } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/BatteryFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/BatteryFragment.kt index 4998ebd8..ee54d837 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/BatteryFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/BatteryFragment.kt @@ -1,5 +1,6 @@ package com.openvehicles.OVMS.ui +import android.content.res.Configuration import android.graphics.Color import android.graphics.Paint import android.os.Bundle @@ -16,6 +17,7 @@ import android.view.ViewGroup import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import androidx.appcompat.app.AlertDialog +import androidx.core.graphics.ColorUtils import com.github.mikephil.charting.charts.CandleStickChart import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.components.LimitLine @@ -33,6 +35,7 @@ import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.google.android.material.color.MaterialColors import com.openvehicles.OVMS.R import com.openvehicles.OVMS.entities.BatteryData import com.openvehicles.OVMS.entities.BatteryData.CellStatus @@ -78,6 +81,16 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa private var carData: CarData? = null private var cmdSeries: CmdSeries? = null private lateinit var appPrefs: AppPrefs + + private var colorText = Color.WHITE + private var colorGrid = Color.GRAY + private var colorSocLine = COLOR_SOC_LINE + private var colorSocText = COLOR_SOC_TEXT + private var colorSocGrid = COLOR_SOC_GRID + private var colorVolt = COLOR_VOLT + private var colorVoltGrid = COLOR_VOLT_GRID + private var colorTemp = COLOR_TEMP + private var colorTempGrid = COLOR_TEMP_GRID private val isPackValid: Boolean // Check data model validity @@ -101,6 +114,22 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa showVolt = true } + // Init theme colors: + val ctx = requireContext() + colorText = MaterialColors.getColor(ctx, android.R.attr.textColorPrimary, Color.WHITE) + colorGrid = ColorUtils.setAlphaComponent(colorText, 40) + + val isNight = (ctx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + if (!isNight) { + colorSocLine = Color.parseColor("#A03344CC") + colorSocText = Color.parseColor("#8888CC") + colorSocGrid = ColorUtils.setAlphaComponent(colorSocText, 100) + colorVolt = Color.parseColor("#669900") + colorVoltGrid = ColorUtils.setAlphaComponent(colorVolt, 100) + colorTemp = Color.parseColor("#998800") + colorTempGrid = ColorUtils.setAlphaComponent(colorTemp, 100) + } + // Setup UI: @@ -112,6 +141,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa // Setup Cell status chart: // cellChart = (rootView.findViewById(R.id.chart_cells) as CandleStickChart) + cellChart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) cellChart.apply { description = cellChart.description.apply { text = getString(R.string.battery_cell_description) @@ -121,7 +151,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa setDrawBorders(true) } var xAxis: XAxis = cellChart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.valueFormatter = object : ValueFormatter() { override fun getFormattedValue(value: Float): String { return "#" + (value.toInt() + 1) @@ -130,19 +160,20 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa xAxis.isGranularityEnabled = true xAxis.setGranularity(1f) var yAxis: YAxis = cellChart.axisLeft - yAxis.textColor = COLOR_VOLT - yAxis.gridColor = COLOR_VOLT_GRID + yAxis.textColor = colorVolt + yAxis.gridColor = colorVoltGrid yAxis.valueFormatter = DefaultAxisValueFormatter(2) yAxis.setGranularity(0.01f) yAxis = cellChart.axisRight - yAxis.textColor = COLOR_TEMP - yAxis.gridColor = COLOR_TEMP_GRID + yAxis.textColor = colorTemp + yAxis.gridColor = colorTempGrid yAxis.valueFormatter = DefaultAxisValueFormatter(0) // // Setup Pack history chart: // packChart = rootView.findViewById(R.id.chart_pack) as LineChart + packChart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) packChart.description.isEnabled = false packChart.setDrawGridBackground(false) packChart.setDrawBorders(true) @@ -165,15 +196,15 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa } }) xAxis = packChart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.valueFormatter = PackTimeValueFormatter() xAxis.isGranularityEnabled = true xAxis.setGranularity(1f) yAxis = packChart.axisLeft yAxis.spaceTop = 5f yAxis.spaceBottom = 5f - yAxis.textColor = COLOR_SOC_TEXT - yAxis.gridColor = COLOR_SOC_GRID + yAxis.textColor = colorSocText + yAxis.gridColor = colorSocGrid yAxis.valueFormatter = object : DefaultAxisValueFormatter(0) { override fun getFormattedValue(value: Float): String { return super.getFormattedValue(value) + "%" @@ -182,8 +213,8 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa yAxis = packChart.axisRight yAxis.spaceTop = 15f yAxis.spaceBottom = 15f - yAxis.textColor = COLOR_VOLT - yAxis.gridColor = COLOR_VOLT_GRID + yAxis.textColor = colorVolt + yAxis.gridColor = colorVoltGrid yAxis.valueFormatter = object : DefaultAxisValueFormatter(1) { override fun getFormattedValue(value: Float): String { return super.getFormattedValue(value) + "%" @@ -446,7 +477,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa if (packStatus.isNewSection(lastStatus)) { val limitLine = LimitLine(xPos) limitLine.label = timeFmt.format(packStatus.timeStamp) - limitLine.textColor = Color.WHITE + limitLine.textColor = colorText limitLine.textStyle = Paint.Style.FILL limitLine.setTextSize(6f) limitLine.enableDashedLine(3f, 2f, 0f) @@ -464,7 +495,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa dataSet = LineDataSet(tempValues, getString(R.string.battery_data_temp)) packTempSet = dataSet dataSet.axisDependency = YAxis.AxisDependency.RIGHT - dataSet.setColor(COLOR_TEMP) + dataSet.setColor(colorTemp) dataSet.setLineWidth(3f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -482,7 +513,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa dataSet = LineDataSet(voltValues, getString(R.string.battery_data_volt)) packVoltSet = dataSet dataSet.axisDependency = YAxis.AxisDependency.RIGHT - dataSet.setColor(COLOR_VOLT) + dataSet.setColor(colorVolt) dataSet.setLineWidth(3f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -490,7 +521,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa } dataSet = LineDataSet(socValues, getString(R.string.battery_data_soc)) dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.setColor(COLOR_SOC_LINE) + dataSet.setColor(colorSocLine) dataSet.setLineWidth(4f) dataSet.setDrawCircles(false) dataSet.setDrawValues(false) @@ -499,7 +530,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa // display data sets: val data: LineData = LineData(dataSets) packData = data - data.setValueTextColor(Color.WHITE) + data.setValueTextColor(colorText) data.setValueTextSize(9f) packChart.setData(data) val xAxis = packChart.xAxis @@ -513,7 +544,7 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa } xAxis.addLimitLine(l) } - packChart.legend.textColor = Color.WHITE + packChart.legend.textColor = colorText packChart.invalidate() } @@ -626,10 +657,10 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa if (showTemp) { dataSet = CandleDataSet(tempValues, getString(R.string.battery_data_temp)) dataSet.axisDependency = YAxis.AxisDependency.RIGHT - dataSet.neutralColor = COLOR_TEMP - dataSet.increasingColor = COLOR_TEMP - dataSet.decreasingColor = COLOR_TEMP - dataSet.shadowColor = COLOR_TEMP + dataSet.neutralColor = colorTemp + dataSet.increasingColor = colorTemp + dataSet.decreasingColor = colorTemp + dataSet.shadowColor = colorTemp dataSet.setDrawValues(true) dataSet.shadowWidth = 4f dataSet.valueFormatter = object : ValueFormatter() { @@ -642,10 +673,10 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa if (showVolt) { dataSet = CandleDataSet(voltValues, getString(R.string.battery_data_volt)) dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.neutralColor = COLOR_VOLT - dataSet.increasingColor = COLOR_VOLT - dataSet.decreasingColor = COLOR_VOLT - dataSet.shadowColor = COLOR_VOLT + dataSet.neutralColor = colorVolt + dataSet.increasingColor = colorVolt + dataSet.decreasingColor = colorVolt + dataSet.shadowColor = colorVolt dataSet.setDrawValues(true) dataSet.shadowWidth = 4f dataSet.valueFormatter = object : ValueFormatter() { @@ -688,10 +719,10 @@ class BatteryFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCa // display data sets: val data = CandleData(dataSets) - data.setValueTextColor(Color.WHITE) + data.setValueTextColor(colorText) data.setValueTextSize(9f) cellChart.setData(data) - cellChart.legend.textColor = Color.WHITE + cellChart.legend.textColor = colorText cellChart.invalidate() } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/CarFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/CarFragment.kt index 9a089d32..61dc696b 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/CarFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/CarFragment.kt @@ -78,7 +78,12 @@ class CarFragment : BaseFragment(), View.OnClickListener, OnResultCommandListene // set the car background image: val iv = findViewById(R.id.tabCarImageCarOutline) as ImageView - iv.setImageResource(getDrawableIdentifier(activity, "ol_" + this.carData!!.sel_vehicle_image)) + val customDrawable = Ui.getCarDrawable(activity, this.carData!!.sel_vehicle_image) + if (customDrawable != null) { + iv.setImageDrawable(customDrawable) + } else { + iv.setImageResource(getDrawableIdentifier(activity, "ol_" + this.carData!!.sel_vehicle_image)) + } val tabCarImageHomeLink: ImageView = findViewById(R.id.tabCarImageHomelink) as ImageView when (carData.car_type) { "RT" -> { diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/InfoFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/InfoFragment.kt index dac05608..d93a7446 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/InfoFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/InfoFragment.kt @@ -933,12 +933,17 @@ class InfoFragment : BaseFragment(), View.OnClickListener, OnResultCommandListen ) iv.setScaleType(ImageView.ScaleType.FIT_START) iv.setAdjustViewBounds(true) - iv.setImageResource( - getDrawableIdentifier( - parent.context, - carData.sel_vehicle_image + val customDrawable = Ui.getCarDrawable(parent.context, carData.sel_vehicle_image) + if (customDrawable != null) { + iv.setImageDrawable(customDrawable) + } else { + iv.setImageResource( + getDrawableIdentifier( + parent.context, + carData.sel_vehicle_image + ) ) - ) + } iv } catch (e: Exception) { null diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/MainActivity.kt b/app/src/main/java/com/openvehicles/OVMS/ui/MainActivity.kt index 235da89b..aafe1a78 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/MainActivity.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/MainActivity.kt @@ -42,6 +42,7 @@ import com.openvehicles.OVMS.api.ApiService import com.openvehicles.OVMS.ui.BaseFragmentActivity.Companion.finishCurrent import com.openvehicles.OVMS.ui.MapFragment.UpdateLocation import com.openvehicles.OVMS.ui.utils.Database +import com.openvehicles.OVMS.ui2.MainActivityUI2 import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.utils.ConnectionList import com.openvehicles.OVMS.utils.ConnectionList.ConnectionsListener @@ -164,6 +165,15 @@ class MainActivity : ApiActivity(), ActionBar.OnNavigationListener, GetMapDetail super.onCreate(savedInstanceState) Log.d(TAG, "onCreate") appPrefs = AppPrefs(this, "ovms") + + val isOldUI = appPrefs.getData("option_oldui_enabled", "0") == "1" + if (!isOldUI) { + Log.i(TAG, "onCreate: redirection to New UI (MainActivityUI2)") + startActivity(Intent(this, MainActivityUI2::class.java)) + finish() + return + } + database = Database(this) // get/create App UUID: @@ -294,9 +304,19 @@ class MainActivity : ApiActivity(), ActionBar.OnNavigationListener, GetMapDetail override fun onDestroy() { Log.d(TAG, "onDestroy") - unregisterReceiver(apiEventReceiver) - unregisterReceiver(notificationReceiver) - database.close() + try { + unregisterReceiver(apiEventReceiver) + } catch (e: Exception) { + // ignore + } + try { + unregisterReceiver(notificationReceiver) + } catch (e: Exception) { + // ignore + } + if (::database.isInitialized) { + database.close() + } // Stop background ApiService? val serviceEnabled = appPrefs.getData("option_service_enabled", "0") == "1" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/MapFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/MapFragment.kt index 4d875c9c..7ec88a14 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/MapFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/MapFragment.kt @@ -40,6 +40,8 @@ import com.openvehicles.OVMS.entities.CarData import com.openvehicles.OVMS.ui.BaseFragmentActivity.Companion.show import com.openvehicles.OVMS.ui.MapSettingsFragment.UpdateMap import com.openvehicles.OVMS.ui.utils.Database +import com.openvehicles.OVMS.ui.utils.Ui +import android.graphics.Bitmap import com.openvehicles.OVMS.ui.utils.DemoClusterOptionsProvider import com.openvehicles.OVMS.ui.utils.MarkerGenerator.addMarkers import com.openvehicles.OVMS.ui.utils.Ui.getDrawableIdentifier @@ -112,6 +114,12 @@ class MapFragment : BaseFragment(), GoogleMap.OnInfoWindowClickListener, GetMapD map = googleMap Log.i(TAG, "getMap/onMapReady: map=$map") + + // Set map type from prefs + val mapTypeStr = appPrefs.getData("map_type", GoogleMap.MAP_TYPE_NORMAL.toString()) + val mapType = mapTypeStr?.toIntOrNull() ?: GoogleMap.MAP_TYPE_NORMAL + map!!.mapType = mapType + var clusterEnabled = true var clusterSizeIndex = 0 try { @@ -204,7 +212,7 @@ class MapFragment : BaseFragment(), GoogleMap.OnInfoWindowClickListener, GetMapD // fetch chargepoints for view: val cameraPosition = map!!.cameraPosition Log.i(TAG, "getMap/onCameraIdle: get charge points for " + cameraPosition.target) - //mainActivity.startGetMapDetails(cameraPosition.target) + (activity as? MainActivityUI2)?.startGetMapDetails(cameraPosition.target) }) update() } @@ -251,6 +259,16 @@ class MapFragment : BaseFragment(), GoogleMap.OnInfoWindowClickListener, GetMapD optionsMenu.findItem(R.id.mi_map_filter_range) .setVisible(false) } + + // Set checked map type + val mapTypeStr = appPrefs.getData("map_type", GoogleMap.MAP_TYPE_NORMAL.toString()) + val mapType = mapTypeStr?.toIntOrNull() ?: GoogleMap.MAP_TYPE_NORMAL + when (mapType) { + GoogleMap.MAP_TYPE_NORMAL -> optionsMenu.findItem(R.id.mi_map_type_normal)?.setChecked(true) + GoogleMap.MAP_TYPE_SATELLITE -> optionsMenu.findItem(R.id.mi_map_type_satellite)?.setChecked(true) + GoogleMap.MAP_TYPE_HYBRID -> optionsMenu.findItem(R.id.mi_map_type_hybrid)?.setChecked(true) + GoogleMap.MAP_TYPE_TERRAIN -> optionsMenu.findItem(R.id.mi_map_type_terrain)?.setChecked(true) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -275,6 +293,29 @@ class MapFragment : BaseFragment(), GoogleMap.OnInfoWindowClickListener, GetMapD appPrefs.saveData("inrange", if (newState) "on" else "off") item.setChecked(newState) updateMapDetails(false) + } else if (menuId == R.id.mi_map_type_normal || menuId == R.id.mi_map_type_satellite || + menuId == R.id.mi_map_type_hybrid || menuId == R.id.mi_map_type_terrain + ) { + val type = when (menuId) { + R.id.mi_map_type_satellite -> GoogleMap.MAP_TYPE_SATELLITE + R.id.mi_map_type_hybrid -> GoogleMap.MAP_TYPE_HYBRID + R.id.mi_map_type_terrain -> GoogleMap.MAP_TYPE_TERRAIN + else -> GoogleMap.MAP_TYPE_NORMAL + } + map?.mapType = type + appPrefs.saveData("map_type", type.toString()) + item.setChecked(true) + } else if (menuId == R.id.mi_map_search) { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("geo:${carPosition.latitude},${carPosition.longitude}?q=charging+station") + ) + intent.setPackage("com.google.android.apps.maps") + if (intent.resolveActivity(requireContext().packageManager) != null) { + startActivity(intent) + } else { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=charging+station"))) + } } else if (menuId == R.id.mi_map_settings) { var baseActivity: MainActivity? = null try { @@ -469,23 +510,52 @@ class MapFragment : BaseFragment(), GoogleMap.OnInfoWindowClickListener, GetMapD // update car position marker: // determine icon for this car: - val icon: Int = if (carData!!.sel_vehicle_image.startsWith("car_imiev_")) R.drawable.map_car_imiev // one map icon for all colors - else if (carData!!.sel_vehicle_image.startsWith("car_i3_")) R.drawable.map_car_i3 // one map icon for all colors - else if (carData!!.sel_vehicle_image.startsWith("car_smart_")) R.drawable.map_car_smart // one map icon for all colors - else if (carData!!.sel_vehicle_image.startsWith("car_kianiro_")) R.drawable.map_car_kianiro_grey // one map icon for all colors - else if (carData!!.sel_vehicle_image.startsWith("car_kangoo_")) R.drawable.map_car_kangoo // one map icon for all colors - else if (carData!!.sel_vehicle_image.startsWith("car_nrjk")) R.drawable.map_car_nrjk - else if (carData!!.sel_vehicle_image.startsWith("car_niu_mqi_gt_")) R.drawable.map_car_nrjk - else getDrawableIdentifier(activity, "map_" + carData!!.sel_vehicle_image) - val drawable = ResourcesCompat.getDrawable( - resources, - if (icon != 0) icon else R.drawable.map_car_default, null - ) - val myLogo = (drawable as BitmapDrawable?)!!.bitmap + var myLogo: Bitmap? = null + if (carData!!.sel_vehicle_image_map.startsWith("file://")) { + val customDrawable = Ui.getCarDrawable(requireContext(), carData!!.sel_vehicle_image_map) + if (customDrawable is BitmapDrawable) { + myLogo = customDrawable.bitmap + // Scale down if too large? + if (myLogo.width > 116 || myLogo.height > 116) { + myLogo = Bitmap.createScaledBitmap(myLogo, 116, 116, true) + } + } + } + + if (myLogo == null) { + val icon: Int = + if (carData!!.sel_vehicle_image_map.isNotEmpty() && !carData!!.sel_vehicle_image_map.startsWith("file://")) { + getDrawableIdentifier(activity, carData!!.sel_vehicle_image_map) + } else if (carData!!.sel_vehicle_image.startsWith("car_imiev_")) { + R.drawable.map_car_imiev // one map icon for all colors + } else if (carData!!.sel_vehicle_image.startsWith("car_i3_")) { + R.drawable.map_car_i3 // one map icon for all colors + } else if (carData!!.sel_vehicle_image.startsWith("car_smart_")) { + R.drawable.map_car_smart // one map icon for all colors + } else if (carData!!.sel_vehicle_image.startsWith("car_kianiro_")) { + R.drawable.map_car_kianiro_grey // one map icon for all colors + } else if (carData!!.sel_vehicle_image.startsWith("car_kangoo_")) { + R.drawable.map_car_kangoo // one map icon for all colors + } else if (carData!!.sel_vehicle_image.startsWith("car_nrjk")) { + R.drawable.map_car_nrjk + } else if (carData!!.sel_vehicle_image.startsWith("car_niu_mqi_gt_")) { + R.drawable.map_car_nrjk + } else { + getDrawableIdentifier(activity, "map_" + carData!!.sel_vehicle_image) + } + + val drawable = ResourcesCompat.getDrawable( + resources, + if (icon != 0) icon else R.drawable.map_car_default, null + ) + myLogo = (drawable as BitmapDrawable?)!!.bitmap + myLogo = Bitmap.createScaledBitmap(myLogo, 116, 116, true) + } + val marker = MarkerOptions().position(carPosition) .title(carData!!.sel_vehicle_label) .rotation(carData!!.car_direction.toFloat()) - .icon(BitmapDescriptorFactory.fromBitmap(myLogo)) + .icon(BitmapDescriptorFactory.fromBitmap(myLogo!!)) val carMarker = map!!.addMarker(marker) carMarker.clusterGroup = ClusterGroup.NOT_CLUSTERED diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/PowerFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/PowerFragment.kt index b2589824..b5474c13 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/PowerFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/PowerFragment.kt @@ -1,5 +1,6 @@ package com.openvehicles.OVMS.ui +import android.content.res.Configuration import android.graphics.Color import android.graphics.Matrix import android.graphics.Paint @@ -16,6 +17,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog +import androidx.core.graphics.ColorUtils import com.github.mikephil.charting.charts.BarLineChartBase import com.github.mikephil.charting.charts.Chart import com.github.mikephil.charting.charts.LineChart @@ -29,6 +31,7 @@ import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture import com.github.mikephil.charting.listener.OnChartGestureListener +import com.google.android.material.color.MaterialColors import com.openvehicles.OVMS.R import com.openvehicles.OVMS.entities.CarData import com.openvehicles.OVMS.entities.CmdSeries @@ -60,6 +63,15 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc private var carData: CarData? = null private var cmdSeries: CmdSeries? = null private lateinit var appPrefs: AppPrefs + + private var colorText = Color.WHITE + private var colorGrid = Color.GRAY + private var colorPowerAvg = COLOR_POWER_AVG + private var colorEnergyAvg = COLOR_ENERGY_AVG + private var colorAltitude = COLOR_ALTITUDE + private var colorAltitudeGrid = COLOR_ALTITUDE_GRID + private var colorSpeed = COLOR_SPEED + private var colorSpeedGrid = COLOR_SPEED_GRID private val isPackValid: Boolean // Check data model validity @@ -81,6 +93,21 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc showPower = true } + // Init theme colors: + val ctx = requireContext() + colorText = MaterialColors.getColor(ctx, android.R.attr.textColorPrimary, Color.WHITE) + colorGrid = ColorUtils.setAlphaComponent(colorText, 40) + + val isNight = (ctx.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + if (!isNight) { + colorPowerAvg = Color.RED + colorEnergyAvg = Color.parseColor("#999900") + colorAltitude = Color.parseColor("#998833") + colorAltitudeGrid = ColorUtils.setAlphaComponent(colorAltitude, 100) + colorSpeed = Color.parseColor("#333399") + colorSpeedGrid = ColorUtils.setAlphaComponent(colorSpeed, 100) + } + // Setup UI: //val progressOverlay = createProgressOverlay(inflater, container, false) //progressOverlay.setOnCancelListener(this) @@ -93,23 +120,24 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc // Setup trip chart: // var chart: LineChart = rootView.findViewById(R.id.chart_trip) as LineChart + chart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) tripChart = chart chart.description.isEnabled = false chart.setDrawGridBackground(false) chart.setDrawBorders(true) chart.isHighlightPerTapEnabled = false xAxis = chart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.valueFormatter = xFormatter xAxis.granularity = 1f xAxis.isGranularityEnabled = true yAxis = chart.axisLeft // altitude - yAxis.textColor = COLOR_ALTITUDE - yAxis.gridColor = COLOR_ALTITUDE_GRID + yAxis.textColor = colorAltitude + yAxis.gridColor = colorAltitudeGrid yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) yAxis = chart.axisRight // speed - yAxis.textColor = COLOR_SPEED - yAxis.gridColor = COLOR_SPEED_GRID + yAxis.textColor = colorSpeed + yAxis.gridColor = colorSpeedGrid yAxis.axisMinimum = 0f yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) @@ -117,47 +145,49 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc // Setup power chart: // chart = rootView.findViewById(R.id.chart_power) as LineChart + chart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) powerChart = chart chart.description.isEnabled = false chart.setDrawGridBackground(false) chart.setDrawBorders(true) chart.isHighlightPerTapEnabled = false xAxis = chart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.valueFormatter = xFormatter xAxis.granularity = 1f xAxis.isGranularityEnabled = true yAxis = chart.axisLeft - yAxis.textColor = Color.WHITE - yAxis.gridColor = Color.LTGRAY + yAxis.textColor = colorText + yAxis.gridColor = colorGrid yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) yAxis.setDrawTopYLabelEntry(false) yAxis = chart.axisRight - yAxis.textColor = Color.WHITE - yAxis.gridColor = Color.LTGRAY + yAxis.textColor = colorText + yAxis.gridColor = colorGrid yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) // // Setup energy chart: // chart = rootView.findViewById(R.id.chart_energy) as LineChart + chart.setLayerType(View.LAYER_TYPE_SOFTWARE, null) energyChart = chart chart.description.isEnabled = false chart.setDrawGridBackground(false) chart.setDrawBorders(true) chart.isHighlightPerTapEnabled = false xAxis = chart.xAxis - xAxis.textColor = Color.WHITE + xAxis.textColor = colorText xAxis.valueFormatter = xFormatter xAxis.granularity = 1f xAxis.isGranularityEnabled = true yAxis = chart.axisLeft - yAxis.textColor = Color.WHITE - yAxis.gridColor = Color.LTGRAY + yAxis.textColor = colorText + yAxis.gridColor = colorGrid yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) yAxis = chart.axisRight - yAxis.textColor = Color.WHITE - yAxis.gridColor = Color.LTGRAY + yAxis.textColor = colorText + yAxis.gridColor = colorGrid yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART) // @@ -451,7 +481,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc if (entry.isSectionStart(refEntry)) { val l = LimitLine(xpos) l.label = String.format("%.0f", entry.getOdometer(units)) - l.textColor = Color.WHITE + l.textColor = colorText l.textStyle = Paint.Style.FILL l.textSize = 6f l.enableDashedLine(3f, 2f, 0f) @@ -497,7 +527,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc // display data sets: var data: LineData data = LineData(dataSets) - data.setValueTextColor(Color.WHITE) + data.setValueTextColor(colorText) data.setValueTextSize(9f) tripChart.data = data xAxis = tripChart.xAxis @@ -511,7 +541,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc } xAxis.addLimitLine(l) } - tripChart.legend.textColor = Color.WHITE + tripChart.legend.textColor = colorText tripChart.invalidate() // @@ -543,7 +573,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc dataSets.add(dataSet) dataSet = LineDataSet(pwrAvgValues, getString(R.string.power_data_pwr_avg)) dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.color = COLOR_POWER_AVG + dataSet.color = colorPowerAvg dataSet.setDrawFilled(false) dataSet.lineWidth = 4f dataSet.setDrawCircles(false) @@ -626,7 +656,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc getString(R.string.power_data_energy_avg, carData!!.car_distance_units) ) dataSet.axisDependency = YAxis.AxisDependency.LEFT - dataSet.color = COLOR_ENERGY_AVG + dataSet.color = colorEnergyAvg dataSet.setDrawFilled(false) dataSet.lineWidth = 4f dataSet.setDrawCircles(false) @@ -637,7 +667,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc // display data sets: data = LineData(dataSets) - data.setValueTextColor(Color.WHITE) + data.setValueTextColor(colorText) data.setValueTextSize(9f) energyChart.data = data xAxis = energyChart.xAxis @@ -651,7 +681,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc } xAxis.addLimitLine(l) } - energyChart.legend.textColor = Color.WHITE + energyChart.legend.textColor = colorText energyChart.invalidate() } } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/settings/GlobalOptionsFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/settings/GlobalOptionsFragment.kt index c82ec4d3..9207208a 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/settings/GlobalOptionsFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/settings/GlobalOptionsFragment.kt @@ -20,6 +20,7 @@ import com.openvehicles.OVMS.api.ApiService import com.openvehicles.OVMS.ui.BaseFragment import com.openvehicles.OVMS.ui.utils.Ui.setOnClick import com.openvehicles.OVMS.ui.utils.Ui.setValue +import com.openvehicles.OVMS.ui2.MainActivityUI2 import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.utils.Sys.getRandomString @@ -88,6 +89,12 @@ class GlobalOptionsFragment : BaseFragment(), View.OnClickListener, OnFocusChang R.id.cb_options_oldui -> { oldUiEnabled = (v as CheckBox).isChecked appPrefs!!.saveData("option_oldui_enabled", if (oldUiEnabled) "1" else "0") + if (!oldUiEnabled) { + // Switch to new UI immediately: + val intent = Intent(context, MainActivityUI2::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + startActivity(intent) + } } R.id.cb_options_service -> { serviceEnabled = (v as CheckBox).isChecked diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/settings/StoredCommandFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui/settings/StoredCommandFragment.kt index 4f08655f..eb72e6a5 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui/settings/StoredCommandFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/settings/StoredCommandFragment.kt @@ -2,7 +2,6 @@ package com.openvehicles.OVMS.ui.settings import android.app.Activity import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.os.Build import android.os.Bundle @@ -18,7 +17,7 @@ import android.widget.AdapterView.OnItemClickListener import android.widget.ArrayAdapter import android.widget.ListView import android.widget.Toast -import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.res.ResourcesCompat @@ -55,9 +54,6 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - if (container == null) { - return null - } val context = context ?: return null @@ -75,12 +71,13 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { // Create user interface: activity?.setTitle(R.string.stored_commands_title) setHasOptionsMenu(true) - listView = ListView(container.context) - listView.fitsSystemWindows = true + + val rootView = inflater.inflate(R.layout.fragment_stored_commands, container, false) + listView = rootView.findViewById(R.id.listView) listView.onItemClickListener = this adapter = StoredCommandAdapter(context, R.layout.item_stored_command, storedCommands, inflater) listView.setAdapter(adapter) - return listView + return rootView } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -118,7 +115,7 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { private fun confirmDeleteCommand(cmd: StoredCommand) { val context = context ?: return - AlertDialog.Builder(context) + MaterialAlertDialogBuilder(context) .setTitle(R.string.Delete) .setMessage(getString(R.string.confirm_delete_stored_command, cmd.title)) .setNegativeButton(R.string.Cancel, null) @@ -132,14 +129,14 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { val context = context ?: return - val view = LayoutInflater.from(context).inflate(R.layout.dlg_stored_command, null) + val view = LayoutInflater.from(context).inflate(R.layout.dlg_stored_command, null, false) if (cmd == null) { // Create new command: - val dialog = AlertDialog.Builder(context) + val dialog = MaterialAlertDialogBuilder(context) .setMessage(R.string.stored_commands_add) .setView(view) .setNegativeButton(R.string.Cancel, null) - .setPositiveButton(R.string.Save) { dialog1: DialogInterface?, which: Int -> + .setPositiveButton(R.string.Save) { _, _ -> val cmd1 = StoredCommand( getValue(view, R.id.etxt_input_title), getValue(view, R.id.etxt_input_command) @@ -153,15 +150,15 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { view.tag = cmd setValue(view, R.id.etxt_input_title, cmd.title) setValue(view, R.id.etxt_input_command, cmd.command) - val dialog = AlertDialog.Builder(context) + val dialog = MaterialAlertDialogBuilder(context) .setMessage(R.string.stored_commands_edit) .setView(view) .setNegativeButton(R.string.Cancel, null) - .setNeutralButton(R.string.Delete) { dialog12: DialogInterface?, which: Int -> + .setNeutralButton(R.string.Delete) { _, _ -> val cmd12 = view.tag as StoredCommand deleteCommand(cmd12) } - .setPositiveButton(R.string.Save) { dialog13: DialogInterface?, which: Int -> + .setPositiveButton(R.string.Save) { _, _ -> val cmd13 = view.tag as StoredCommand cmd13.title = getValue(view, R.id.etxt_input_title) cmd13.command = getValue(view, R.id.etxt_input_command) @@ -266,7 +263,7 @@ class StoredCommandFragment : BaseFragment(), OnItemClickListener { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var view = convertView if (view == null) { - view = inflater.inflate(R.layout.item_stored_command, null) + view = inflater.inflate(R.layout.item_stored_command, parent, false) } setOnClick(view!!, R.id.linearLayout1, position, this, null) setOnClick(view, R.id.btn_select, position, this, null) diff --git a/app/src/main/java/com/openvehicles/OVMS/ui/utils/Ui.kt b/app/src/main/java/com/openvehicles/OVMS/ui/utils/Ui.kt index 288da0b4..9f10d2fe 100755 --- a/app/src/main/java/com/openvehicles/OVMS/ui/utils/Ui.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui/utils/Ui.kt @@ -21,6 +21,11 @@ import com.google.android.material.textfield.TextInputEditText import com.openvehicles.OVMS.R import com.openvehicles.OVMS.ui.validators.ValidationException import com.openvehicles.OVMS.ui.validators.Validator +import androidx.core.content.ContextCompat + +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import java.io.File object Ui { @@ -146,6 +151,29 @@ object Ui { } } + @JvmStatic + fun getCarDrawable(context: Context?, name: String?): Drawable? { + if (name == null || context == null) return null + + if (name.startsWith("file://")) { + val path = name.substring(7) + val file = File(path) + if (file.exists()) { + val bitmap = BitmapFactory.decodeFile(path) + if (bitmap != null) { + return BitmapDrawable(context.resources, bitmap) + } + } + } + + val resId = getDrawableIdentifier(context, name) + return if (resId > 0) { + ContextCompat.getDrawable(context, resId) + } else { + null + } + } + @JvmStatic fun getStringIdentifier(context: Context?, name: String?): Int { return if (name == null || context == null) { diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/MainActivityUI2.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/MainActivityUI2.kt index 228349bb..d9131922 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/MainActivityUI2.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/MainActivityUI2.kt @@ -7,6 +7,8 @@ import android.content.DialogInterface import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.content.res.Configuration +import android.graphics.Color import android.os.Build import android.os.Bundle import android.os.Handler @@ -20,7 +22,12 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.navigation.NavController +import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.NavigationUI @@ -28,6 +35,7 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.tasks.OnCompleteListener import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions @@ -36,12 +44,18 @@ import com.openvehicles.OVMS.R import com.openvehicles.OVMS.api.ApiService import com.openvehicles.OVMS.ui.ApiActivity import com.openvehicles.OVMS.ui.MainActivity +import com.google.android.gms.maps.model.LatLng +import com.openvehicles.OVMS.ui.GetMapDetails +import com.openvehicles.OVMS.ui.GetMapDetailsListener import com.openvehicles.OVMS.ui.MapFragment +import com.openvehicles.OVMS.ui.utils.Database +import com.openvehicles.OVMS.ui2.misc.ThemeMode import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.utils.Sys.getRandomString import java.util.UUID +import android.os.AsyncTask -class MainActivityUI2 : ApiActivity() { +class MainActivityUI2 : ApiActivity(), GetMapDetailsListener { companion object { var updateLocation: MapFragment.UpdateLocation? = null @@ -50,6 +64,7 @@ class MainActivityUI2 : ApiActivity() { } private lateinit var navController: NavController + /** * ApiService / OVMS server communication: * @@ -57,6 +72,7 @@ class MainActivityUI2 : ApiActivity() { private var apiErrorDialog: AlertDialog? = null private var apiErrorMessage: String? = null private lateinit var appPrefs: AppPrefs + private lateinit var database: Database private lateinit var uuid: String private var versionName = "" private var versionCode = 0 @@ -64,6 +80,11 @@ class MainActivityUI2 : ApiActivity() { private var tokenRequested = false private var apiEventReceiver: BroadcastReceiver? = null + private var getMapDetails: GetMapDetails? = null + private val getMapDetailList: MutableList = ArrayList() + private var getMapDetailsBlockUntil: Long = 0 + private var getMapDetailsBlockSeconds: Long = 0 + private val gcmHandler = Handler(Looper.getMainLooper()) private val gcmRegistrationBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { @@ -78,12 +99,48 @@ class MainActivityUI2 : ApiActivity() { } private val listener = NavController.OnDestinationChangedListener { controller, destination, arguments -> - findViewById(R.id.spinner_toolbar).visibility = if (destination.id == R.id.navigation_home) View.VISIBLE else View.GONE + val onHome = destination.id == R.id.navigation_home + val spinner = findViewById(R.id.spinner_toolbar) + spinner?.visibility = if (onHome) View.VISIBLE else View.GONE + + supportActionBar?.setDisplayShowTitleEnabled(!onHome) + + updateStatusBarIcons(onHome) + } + + /** + * Applies edge-to-edge rendering. + */ + private fun applyEdgeToEdge() { + WindowCompat.setDecorFitsSystemWindows(window, true) + } + + /** True when the dark theme is currently in effect. */ + private fun isDarkTheme(): Boolean { + val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return mode == Configuration.UI_MODE_NIGHT_YES + } + + /** + * Updates the status bar icon colour. + */ + private fun updateStatusBarIcons(onHome: Boolean) { + val isDark = isDarkTheme() + val controller = WindowInsetsControllerCompat(window, window.decorView) + // Dark icons (isAppearanceLightStatusBars = true) on light background. + controller.isAppearanceLightStatusBars = !isDark } + override fun onCreate(savedInstanceState: Bundle?) { + ThemeMode.apply(this) super.onCreate(savedInstanceState) appPrefs = AppPrefs(this, "ovms") - if (appPrefs.getData("option_oldui_enabled", "0") == "1") { + database = Database(this) + + val isOldUI = appPrefs.getData("option_oldui_enabled", "0") == "1" + Log.d(TAG, "onCreate: option_oldui_enabled = $isOldUI") + if (isOldUI) { + Log.i(TAG, "onCreate: starting Old UI (MainActivity)") startActivity(Intent(this, MainActivity::class.java)) finish() return @@ -159,6 +216,8 @@ class MainActivityUI2 : ApiActivity() { val toolbar: MaterialToolbar = findViewById(R.id.materialToolbar); setSupportActionBar(toolbar) + applyEdgeToEdge() + // Start background ApiService: Log.i(TAG, "onCreate: starting ApiService") try { @@ -178,7 +237,7 @@ class MainActivityUI2 : ApiActivity() { checkVersion() val abc = AppBarConfiguration.Builder(R.id.navigation_home).build(); - val navHostFragment = supportFragmentManager.fragments.first() as NavHostFragment + val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main2) as NavHostFragment navController = navHostFragment.navController NavigationUI.setupActionBarWithNavController(this, navHostFragment.navController, abc) @@ -472,6 +531,82 @@ class MainActivityUI2 : ApiActivity() { return true } + private fun isMapCacheValid(center: LatLng): Boolean { + val latitude = center.latitude.toInt() + val longitude = center.longitude.toInt() + val cursor = database.getLatLngDetail(latitude, longitude) + val colLastUpdate = cursor.getColumnIndex("last_update") + if (cursor.count == 0) { + cursor.close() + return false + } else if (cursor.moveToFirst()) { + val lastUpdate = if (colLastUpdate >= 0) cursor.getLong(colLastUpdate) else 0 + val now = System.currentTimeMillis() / 1000 + if (now > lastUpdate + 3600 * 24) { + cursor.close() + return false + } + } + Log.v(TAG, "isMapCacheValid: cache valid for lat/lng=$latitude/$longitude") + cursor.close() + return true + } + + fun startGetMapDetails(center: LatLng) { + if (!isMapCacheValid(center)) { + getMapDetailList.add(center) + startGetMapDetails() + } else { + Log.v(TAG, "StartGetMapDetails: map cache valid for center=$center") + } + } + + private fun startGetMapDetails() { + if (getMapDetails != null && getMapDetails!!.status != AsyncTask.Status.FINISHED) { + return + } + if (System.currentTimeMillis() < getMapDetailsBlockUntil) { + return + } + if (appPrefs.getData("option_ocm_enabled", "1") == "0") { + return + } + do { + if (getMapDetailList.isEmpty()) { + Log.i(TAG, "StartGetMapDetails: done.") + return + } + val center = getMapDetailList.removeAt(0) + if (center == null || isMapCacheValid(center)) { + continue + } + + Log.i(TAG, "StartGetMapDetails: starting task for $center") + getMapDetails = GetMapDetails(this@MainActivityUI2, center, this) + getMapDetails!!.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + return + } while (true) + } + + override fun getMapDetailsDone(isSuccess: Boolean, center: LatLng?) { + if (isSuccess) { + Log.i(TAG, "getMapDetailsDone: OCM updates received for $center") + database.addLatLngDetail(center!!.latitude.toInt(), center.longitude.toInt()) + + MapFragment.updateMap.updateFilter(null) // trigger update + + getMapDetailsBlockUntil = 0 + getMapDetailsBlockSeconds = 0 + startGetMapDetails() + } else { + Log.e(TAG, "getMapDetailsDone: OCM updates failed for $center") + getMapDetailsBlockSeconds = if (getMapDetailsBlockSeconds == 0L) 15L else getMapDetailsBlockSeconds * 2 + if (getMapDetailsBlockSeconds > 120L) getMapDetailsBlockSeconds = 120L + getMapDetailsBlockUntil = System.currentTimeMillis() + getMapDetailsBlockSeconds * 1000 + startGetMapDetails() + } + } + override fun onResume() { super.onResume() @@ -492,8 +627,14 @@ class MainActivityUI2 : ApiActivity() { override fun onDestroy() { Log.d(TAG, "onDestroy") - if (apiEventReceiver != null) - unregisterReceiver(apiEventReceiver) + database.close() + + try { + if (apiEventReceiver != null) + unregisterReceiver(apiEventReceiver) + } catch (e: Exception) { + // ignore + } // Stop background ApiService? val serviceEnabled = appPrefs.getData("option_service_enabled", "0") == "1" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/energymetrics/EnergyMetricsAdapter.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/energymetrics/EnergyMetricsAdapter.kt index 7378e209..136a40c6 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/energymetrics/EnergyMetricsAdapter.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/energymetrics/EnergyMetricsAdapter.kt @@ -1,14 +1,15 @@ package com.openvehicles.OVMS.ui2.components.energymetrics import android.content.Context -import android.os.Build +import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.core.graphics.ColorUtils import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.color.MaterialColors import com.openvehicles.OVMS.R -import androidx.core.graphics.toColorInt class EnergyMetricsAdapter internal constructor( @@ -30,17 +31,16 @@ class EnergyMetricsAdapter internal constructor( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val metric = mData[position] - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (position.and(1) == 0) - holder.itemView.setBackgroundResource(R.color.material_dynamic_secondary20) - else - holder.itemView.setBackgroundResource(R.color.material_dynamic_secondary10) + + // Zebra striping using theme colors + if (position % 2 == 0) { + holder.itemView.setBackgroundColor(Color.TRANSPARENT) } else { - if (position.and(1) == 0) - holder.itemView.setBackgroundColor("#2A3042".toColorInt()) - else - holder.itemView.setBackgroundColor("#151B2C".toColorInt()) + val baseColor = MaterialColors.getColor(holder.itemView, R.attr.colorOnSurface, Color.BLACK) + val stripeColor = ColorUtils.setAlphaComponent(baseColor, 15) // very subtle overlay + holder.itemView.setBackgroundColor(stripeColor) } + holder.metricName.text = metric.metricName holder.metricValue.text = metric.metricValue holder.clickListener = mClickListener diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/hometabs/HomeTabsAdapter.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/hometabs/HomeTabsAdapter.kt index 8c45608f..4d6ad7b9 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/hometabs/HomeTabsAdapter.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/hometabs/HomeTabsAdapter.kt @@ -45,9 +45,9 @@ class HomeTabsAdapter internal constructor( if (defaultCardColor == null || defaultOnCardColor == null) { val tv = TypedValue() val theme = context.theme - theme.resolveAttribute(com.google.android.material.R.attr.colorSecondaryContainer, tv, true) + theme.resolveAttribute(com.google.android.material.R.attr.colorSurfaceContainer, tv, true) defaultCardColor = tv.data - theme.resolveAttribute(com.google.android.material.R.attr.colorOnSecondaryContainer, tv, true) + theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurface, tv, true) defaultOnCardColor = tv.data } holder.tabName.text = action.tabName diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/CarInfoQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/CarInfoQuickAction.kt index e0845489..7d3f2696 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/CarInfoQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/CarInfoQuickAction.kt @@ -13,8 +13,11 @@ import com.openvehicles.OVMS.ui.settings.CarInfoFragment class CarInfoQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_homelink, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background, - label = context?.getString(R.string.service_notification_title)) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.service_notification_title) + ) { companion object { const val ACTION_ID = "carinfo" } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ChargingQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ChargingQuickAction.kt index d086e88b..8c233800 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ChargingQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ChargingQuickAction.kt @@ -11,7 +11,11 @@ import com.openvehicles.OVMS.api.ApiService class ChargingQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_charging, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background, label = context?.getString(R.string.state_charging_label)) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.state_charging_label) + ) { companion object { const val ACTION_ID = "charging" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateQuickAction.kt index cd49c3e6..da0cd614 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateQuickAction.kt @@ -12,7 +12,9 @@ import com.openvehicles.OVMS.api.ApiService class ClimateQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_ac, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background, + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, label = context?.getString(R.string.climate_control_short)) { companion object { const val ACTION_ID = "climate" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateScheduleQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateScheduleQuickAction.kt index fb472328..2c0f946e 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateScheduleQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ClimateScheduleQuickAction.kt @@ -26,7 +26,9 @@ class ClimateScheduleQuickAction( R.drawable.heat_cool_w, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background, + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, label = context?.getString(R.string.lb_climate_schedule) ) { companion object { diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/DDT4allQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/DDT4allQuickAction.kt index 4d11f797..bb620bca 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/DDT4allQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/DDT4allQuickAction.kt @@ -12,8 +12,11 @@ import com.openvehicles.OVMS.api.ApiService class DDT4allQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_controls_tab, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.attr.colorTertiaryContainer, - label = context?.getString(R.string.select_ddt4all_action)) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.select_ddt4all_action) + ) { companion object { const val ACTION_ID = "ddt4all" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/HomelinkQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/HomelinkQuickAction.kt index 66ef04b9..39db446d 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/HomelinkQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/HomelinkQuickAction.kt @@ -17,7 +17,12 @@ class Homelink2QuickAction(apiServiceGetter: () -> ApiService?, context: Context class Homelink3QuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null): HomelinkQuickAction(2, R.drawable.ic_homelink_3, "${context?.getString(R.string.Garage)} 3", apiServiceGetter) open class HomelinkQuickAction(val garageNumber: Int, icon: Int, label: String?, apiServiceGetter: () -> ApiService?) : - QuickAction("hl_$garageNumber", icon, apiServiceGetter, label = label) { + QuickAction("hl_$garageNumber", icon, apiServiceGetter, + actionOnTint = R.attr.colorSecondaryContainer, + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = label) { override fun onAction() { sendCommand("24,${garageNumber}") diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/LockQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/LockQuickAction.kt index 4a04aac8..0b823252 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/LockQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/LockQuickAction.kt @@ -14,8 +14,11 @@ import com.openvehicles.OVMS.ui.utils.Ui class LockQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_lock_open, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.attr.colorTertiaryContainer, - label = context?.getString(R.string.central_locking_action_label)) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.central_locking_action_label) + ) { companion object { const val ACTION_ID = "lock" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/QuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/QuickAction.kt index 2c76a885..31f13e7d 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/QuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/QuickAction.kt @@ -3,12 +3,14 @@ package com.openvehicles.OVMS.ui2.components.quickactions import android.content.Context import android.content.res.ColorStateList import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.util.TypedValue import android.view.View import android.widget.Toast import androidx.annotation.ColorInt import androidx.appcompat.content.res.AppCompatResources -import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.button.MaterialButton +import com.google.android.material.color.MaterialColors import com.google.android.material.progressindicator.CircularProgressIndicator import com.openvehicles.OVMS.R import com.openvehicles.OVMS.api.ApiService @@ -24,10 +26,12 @@ open class QuickAction { private val icon: Int private var actionOnTint: Int? = null private var actionOffTint: Int? = null + private var actionOnIconTint: Int? = null + private var actionOffIconTint: Int? = null private var carData: CarData? = null private var actionIsRunningCommand = false lateinit var progressBar: CircularProgressIndicator - lateinit var button: FloatingActionButton + lateinit var button: MaterialButton var context: Context? = null private var getApiService: () -> ApiService? @@ -37,12 +41,16 @@ open class QuickAction { getApiService: () -> ApiService?, actionOnTint: Int? = null, actionOffTint: Int? = null, - label: String? = null + label: String? = null, + actionOnIconTint: Int? = null, + actionOffIconTint: Int? = null ) { this.id = id this.icon = icon this.actionOnTint = actionOnTint this.actionOffTint = actionOffTint + this.actionOnIconTint = actionOnIconTint + this.actionOffIconTint = actionOffIconTint this.getApiService = getApiService this.label = label } @@ -51,7 +59,7 @@ open class QuickAction { fun initAction(view: View, clickCallback: () -> Boolean, editMode: Boolean) { // Init elements and let quick action do handling context = view.context - button = view.findViewById(R.id.action_button) as FloatingActionButton + button = view.findViewById(R.id.action_button) as MaterialButton progressBar = view.findViewById(R.id.action_progress) as CircularProgressIndicator button.setOnClickListener { if (clickCallback()) { @@ -72,7 +80,7 @@ open class QuickAction { fun setCommandInProgress(value: Boolean) { this.commandInProgress = value - this.progressBar.visibility = if (value) View.VISIBLE else View.INVISIBLE + this.progressBar.visibility = if (value) View.VISIBLE else View.GONE this.button.isEnabled = commandsAvailable() && !value } @@ -171,6 +179,9 @@ open class QuickAction { open fun renderAction(editMode: Boolean) { val commandsAvailable = commandsAvailable() button.isEnabled = commandsAvailable || editMode + + val iconDrawable = getLiveCarIcon(actionOn, context!!) + if (actionOnTint != null && actionOffTint != null && !editMode) { try { button.backgroundTintList = @@ -184,14 +195,40 @@ open class QuickAction { button.backgroundTintList = ColorStateList.valueOf(color) } + + // Icon Tint Handling for custom states + if (actionOnIconTint != null && actionOffIconTint != null) { + try { + button.iconTint = + context?.resources?.getColor(if (actionOn) actionOnIconTint!! else actionOffIconTint!!) + ?.let { ColorStateList.valueOf(it) } + } catch (ignored: java.lang.Exception) { + val typedValue = TypedValue() + val theme = context!!.theme + theme.resolveAttribute(if (actionOn) actionOnIconTint!! else actionOffIconTint!!, typedValue, true) + @ColorInt val color = typedValue.data + button.iconTint = ColorStateList.valueOf(color) + } + } else if (iconDrawable !is LayerDrawable) { + val typedValue = TypedValue() + context!!.theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true) + button.iconTint = ColorStateList.valueOf(typedValue.data) + } else { + button.iconTint = null + } } else { - val typedValue = TypedValue() - val theme = context!!.theme - theme.resolveAttribute(R.attr.colorPrimarySurface, typedValue, true) - @ColorInt val color = typedValue.data - button.backgroundTintList = - ColorStateList.valueOf(color) + // Use default Material3 Secondary Container colors for edit mode or standard state + val backgroundColor = MaterialColors.getColor(button, R.attr.colorSecondaryContainer) + button.backgroundTintList = ColorStateList.valueOf(backgroundColor) + + if (iconDrawable !is LayerDrawable) { + val iconColor = MaterialColors.getColor(button, R.attr.colorOnSecondaryContainer) + button.iconTint = ColorStateList.valueOf(iconColor) + } else { + button.iconTint = null + } } - button.setImageDrawable(getLiveCarIcon(actionOn, context!!)) + + button.icon = iconDrawable } } \ No newline at end of file diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ValetQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ValetQuickAction.kt index c353cf16..715bad29 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ValetQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/ValetQuickAction.kt @@ -12,8 +12,11 @@ import com.openvehicles.OVMS.ui.utils.Ui class ValetQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : QuickAction(ACTION_ID, R.drawable.ic_valet, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background, - label = context?.getString(R.string.lb_valet_mode)) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.lb_valet_mode) + ) { companion object { const val ACTION_ID = "valet" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/WakeupQuickAction.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/WakeupQuickAction.kt index 055de44e..4e519912 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/WakeupQuickAction.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/WakeupQuickAction.kt @@ -8,9 +8,13 @@ import com.openvehicles.OVMS.api.ApiService * Quick action handling custom commands */ class WakeupQuickAction(apiServiceGetter: () -> ApiService?, context: Context? = null) : - QuickAction(ACTION_ID, R.drawable.ic_car_sleep, apiServiceGetter, label = context?.getString(R.string.Wakeup), + QuickAction(ACTION_ID, R.drawable.ic_car_sleep, apiServiceGetter, actionOnTint = R.attr.colorSecondaryContainer, - actionOffTint = R.color.cardview_dark_background) { + actionOffTint = R.color.cardview_off_background, + actionOnIconTint = R.attr.colorOnSecondaryContainer, + actionOffIconTint = R.color.colorText, + label = context?.getString(R.string.Wakeup), + ) { companion object { const val ACTION_ID = "wakeup" diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/adapters/QuickActionsEditorAdapter.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/adapters/QuickActionsEditorAdapter.kt index 5781e46f..b5ff39e4 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/adapters/QuickActionsEditorAdapter.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/components/quickactions/adapters/QuickActionsEditorAdapter.kt @@ -6,7 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.button.MaterialButton import com.openvehicles.OVMS.R import com.openvehicles.OVMS.ui2.components.quickactions.QuickAction @@ -30,9 +30,9 @@ class QuickActionsEditorAdapter internal constructor( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val action = mData[position] - val button = holder.itemView.findViewById(R.id.action_button) as FloatingActionButton + val button = holder.itemView.findViewById(R.id.action_button) as MaterialButton val label = holder.itemView.findViewById(R.id.quick_action_label) as TextView - button.setImageDrawable(action.getLiveCarIcon(false, holder.itemView.context)) + button.icon = action.getLiveCarIcon(false, holder.itemView.context) button.setOnClickListener { actionClickListener(action.id) } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/misc/ThemeMode.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/misc/ThemeMode.kt new file mode 100644 index 00000000..d2d0f99c --- /dev/null +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/misc/ThemeMode.kt @@ -0,0 +1,57 @@ +package com.openvehicles.OVMS.ui2.misc + +import android.content.Context +import android.util.Log +import androidx.appcompat.app.AppCompatDelegate +import com.openvehicles.OVMS.utils.AppPrefs + +/** + * Light / dark / follow system. + * + * The choice is stored in the "ovms" preferences and applied through + * [AppCompatDelegate], so it survives restarts and applies to every Activity without + * each screen having to know about it. + */ +object ThemeMode { + + const val PREF_KEY = "app_theme_mode" + + const val LIGHT = "light" + const val DARK = "dark" + const val SYSTEM = "system" + + /** Default: follow the system theme. */ + const val DEFAULT = SYSTEM + + fun read(context: Context): String { + val prefs = AppPrefs(context, "ovms") + val value = prefs.getData(PREF_KEY, DEFAULT) + return if (value.isNullOrBlank()) DEFAULT else value + } + + fun write(context: Context, mode: String) { + AppPrefs(context, "ovms").saveData(PREF_KEY, mode) + } + + private fun toDelegateMode(mode: String): Int = when (mode) { + LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + DARK -> AppCompatDelegate.MODE_NIGHT_YES + else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + + /** Applies the stored choice. Call once on application start. */ + fun apply(context: Context) { + val mode = read(context) + Log.d("ThemeMode", "apply: mode=$mode") + AppCompatDelegate.setDefaultNightMode(toDelegateMode(mode)) + } + + /** + * Stores and applies a new choice. Activities recreate themselves, so the change + * is visible immediately. + */ + fun applyAndStore(context: Context, mode: String) { + write(context, mode) + AppCompatDelegate.setDefaultNightMode(toDelegateMode(mode)) + } +} diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ChargingFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ChargingFragment.kt index 397afbe5..f15b9474 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ChargingFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ChargingFragment.kt @@ -201,7 +201,8 @@ class ChargingFragment : BaseFragment(), OnResultCommandListener { val socBattLayers = emptyList().toMutableList() val socBackground = ContextCompat.getDrawable(requireContext(), R.drawable.ic_batt_l0) - socBackground!!.setTint(Color.GRAY) + val greyBlue = ContextCompat.getColor(requireContext(), R.color.colorGreyBlue) + socBackground!!.setTint(greyBlue) socBattLayers += socBackground // Get icon scaling offsets in display density: @@ -244,7 +245,7 @@ class ChargingFragment : BaseFragment(), OnResultCommandListener { } else if (soc <= 20) { layer1Drawable.setTint(Color.YELLOW) } else { - layer1Drawable.setTint(Color.WHITE) + layer1Drawable.setTint(ContextCompat.getColor(requireContext(), R.color.colorDarkgreen)) } socBattLayers += layer1Drawable } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ClimateFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ClimateFragment.kt index d1b46b9b..da003e86 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ClimateFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ClimateFragment.kt @@ -12,6 +12,7 @@ import android.view.animation.AnimationUtils import android.widget.ImageView import android.widget.TextView import android.widget.Toast +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.openvehicles.OVMS.R @@ -118,14 +119,17 @@ class ClimateFragment : BaseFragment(), OnResultCommandListener { insideTempText.alpha = 0.6f } + val errorColor = ContextCompat.getColor(requireContext(), R.color.colorTextError) + val warningColor = ContextCompat.getColor(requireContext(), R.color.chargeOtherColor) + if (dataStale == CarData.DataStale.NoValue) { - staleLabel.setTextColor(Color.RED) + staleLabel.setTextColor(errorColor) staleLabel.text = getString(R.string.no_data).lowercase() staleLabel.visibility = View.VISIBLE } if (dataStale == CarData.DataStale.Stale) { - staleLabel.setTextColor(Color.YELLOW) + staleLabel.setTextColor(warningColor) staleLabel.text = getString(R.string.stale_data).lowercase() staleLabel.visibility = View.VISIBLE } @@ -139,7 +143,7 @@ class ClimateFragment : BaseFragment(), OnResultCommandListener { if (minutes > 0L) { staleLabel.visibility = View.VISIBLE - staleLabel.setTextColor(Color.YELLOW) + staleLabel.setTextColor(warningColor) val periodText: String if (minutes == 1L) { periodText = getText(R.string.min1).toString() diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ControlsFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ControlsFragment.kt index d14664d9..cac92d3f 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ControlsFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/ControlsFragment.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.graphics.Color import android.graphics.drawable.LayerDrawable import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -14,17 +15,25 @@ import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import com.google.android.material.color.MaterialColors import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager.VERTICAL import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.core.view.isVisible import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.materialswitch.MaterialSwitch +import com.google.android.material.textfield.TextInputEditText import com.openvehicles.OVMS.R import com.openvehicles.OVMS.api.CommandActivity import com.openvehicles.OVMS.api.OnResultCommandListener import com.openvehicles.OVMS.entities.CarData import com.openvehicles.OVMS.entities.CarData.DataStale import com.openvehicles.OVMS.ui.BaseFragment +import com.openvehicles.OVMS.ui.utils.Ui import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.ui2.components.quickactions.ChargingQuickAction import com.openvehicles.OVMS.ui2.components.quickactions.CarInfoQuickAction @@ -43,6 +52,8 @@ import com.openvehicles.OVMS.ui2.components.quickactions.adapters.QuickActionsAd import com.openvehicles.OVMS.ui2.rendering.CarRenderingUtils import com.openvehicles.OVMS.utils.CarsStorage import com.openvehicles.OVMS.utils.CarsStorage.getLastSelectedCarId +import java.text.DateFormat +import java.util.Date class ControlsFragment : BaseFragment(), OnResultCommandListener { @@ -53,6 +64,9 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { private lateinit var bottomActionsAdapter: QuickActionsAdapter private lateinit var centerActionsAdapter: QuickActionsAdapter private lateinit var appPrefs: AppPrefs + private var tpmsConfig: MutableMap = mutableMapOf() + private var lastConfigVid: String? = null + private var isLoadingConfig = false override fun onCreateView( @@ -89,44 +103,221 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { initialiseSideActions(carData) initialiseMainActions(carData) initialiseCarRendering(carData) + loadTPMSConfig() + + findViewById(R.id.btnEditTPMSConfig).setOnClickListener { + showEditTPMSConfigDialog() + } + + // Setup collapsible TPMS Config header + val tpmsConfigHeader = findViewById(R.id.tpmsConfigHeader) + val tpmsConfigBody = findViewById(R.id.tpmsConfigBody) + val tpmsConfigChevron = findViewById(R.id.tpmsConfigChevron) + + tpmsConfigHeader.setOnClickListener { + val vehicleId = carData?.sel_vehicleid + val isExpanded = tpmsConfigBody.visibility == View.VISIBLE + val nextVis = if (isExpanded) View.GONE else View.VISIBLE + + tpmsConfigBody.visibility = nextVis + tpmsConfigChevron.rotation = if (isExpanded) 0f else 90f + + appPrefs.saveData("pref_controls_tpms_config_expanded_$vehicleId", if (nextVis == View.VISIBLE) "1" else "0") + } + } + + override fun update(carData: CarData?) { + val carIdChanged = this.carData?.sel_vehicleid != carData?.sel_vehicleid + this.carData = carData + updateServiceInfo(carData) + updateTPMSData(carData) + if (carIdChanged) { + tpmsConfig.clear() + updateTPMSConfigUI() + loadTPMSConfig() + } + sideActionsAdapter.setCarData(carData) + centerActionsAdapter.setCarData(carData) + sideActionsAdapter.notifyDataSetChanged() + centerActionsAdapter.notifyDataSetChanged() + initialiseCarRendering(carData) + } + + private fun loadTPMSConfig() { + val vid = carData?.sel_vehicleid + if (carData?.car_type != "SQ" || vid == null) { + findViewById(R.id.cardTPMSConfig).visibility = View.GONE + return + } + + if (isLoadingConfig && lastConfigVid == vid) return + + isLoadingConfig = true + lastConfigVid = vid + + // Use prefix 7 (command with output) instead of 1 + sendCommand("", "7,config list xsq", object : OnResultCommandListener { + override fun onResultCommand(result: Array) { + isLoadingConfig = false + if (result.size > 2 && result[1] == "0") { + parseTPMSConfig(result[2]) + updateTPMSConfigUI() + } else { + // Try without prefix if 7 fails, or maybe it's just slow + Log.w("ControlsFragment", "TPMS Config load failed with code ${result.getOrNull(1)}") + } + } + }) + } + + private fun parseTPMSConfig(output: String) { + tpmsConfig.clear() + val lines = output.lines() + for (line in lines) { + // More robust parsing: split at first colon, trim both sides + val parts = line.trim().split(":", limit = 2) + if (parts.size == 2) { + val key = parts[0].trim() + val value = parts[1].trim() + if (key.isNotEmpty()) { + tpmsConfig[key] = value + } + } + } + } + + private fun updateTPMSConfigUI() { + if (view == null) return + val summaryView = findViewById(R.id.tpmsConfigSummary) as TextView + val card = findViewById(R.id.cardTPMSConfig) + val body = findViewById(R.id.tpmsConfigBody) + val chevron = findViewById(R.id.tpmsConfigChevron) as ImageView + + if (tpmsConfig.isEmpty()) { + card.visibility = View.GONE + return + } + + val vehicleId = getLastSelectedCarId() + val showTPMS = appPrefs.getData("pref_controls_show_tpms_$vehicleId", "0") == "1" + card.visibility = if (showTPMS) View.VISIBLE else View.GONE + + // Restore expanded state + val isExpanded = appPrefs.getData("pref_controls_tpms_config_expanded_$vehicleId", "0") == "1" + body.visibility = if (isExpanded) View.VISIBLE else View.GONE + chevron.rotation = if (isExpanded) 90f else 0f + + val alert = tpmsConfig["tpms.alert.enable"] ?: "no" + val front = tpmsConfig["tpms.front.pressure"] ?: "0" + val rear = tpmsConfig["tpms.rear.pressure"] ?: "0" + val temp = tpmsConfig["tpms.temp"] ?: "no" + val warnVal = tpmsConfig["tpms.value.warn"] ?: "0" + val alertVal = tpmsConfig["tpms.value.alert"] ?: "0" + + summaryView.text = "\nAlert message: $alert \n\nShow temperatures: $temp \n\nTarget pressure:\n Front: $front kPa • Rear: $rear kPa \n\nDifference from target pressure:\n Warn: $warnVal kPa • Alert: $alertVal kPa" + } + + private fun showEditTPMSConfigDialog() { + val dialogView = layoutInflater.inflate(R.layout.dialog_edit_tpms_config, null) + val swAlert = dialogView.findViewById(R.id.swAlertEnable) + val etFront = dialogView.findViewById(R.id.etFrontPressure) + val etRear = dialogView.findViewById(R.id.etRearPressure) + val swTemp = dialogView.findViewById(R.id.swTempEnable) + val etWarn = dialogView.findViewById(R.id.etWarnValue) + val etAlert = dialogView.findViewById(R.id.etAlertValue) + + swAlert.isChecked = tpmsConfig["tpms.alert.enable"] == "yes" + etFront.setText(tpmsConfig["tpms.front.pressure"] ?: "0") + etRear.setText(tpmsConfig["tpms.rear.pressure"] ?: "0") + swTemp.isChecked = tpmsConfig["tpms.temp"] == "yes" + etWarn.setText(tpmsConfig["tpms.value.warn"] ?: "0") + etAlert.setText(tpmsConfig["tpms.value.alert"] ?: "0") + + MaterialAlertDialogBuilder(requireContext()) + .setTitle("TPMS Config (XSQ)") + .setView(dialogView) + .setPositiveButton(R.string.Save) { _, _ -> + val newConfig = mapOf( + "tpms.alert.enable" to if (swAlert.isChecked) "yes" else "no", + "tpms.front.pressure" to etFront.text.toString(), + "tpms.rear.pressure" to etRear.text.toString(), + "tpms.temp" to if (swTemp.isChecked) "yes" else "no", + "tpms.value.warn" to etWarn.text.toString(), + "tpms.value.alert" to etAlert.text.toString() + ) + saveTPMSConfig(newConfig) + } + .setNegativeButton(R.string.Cancel, null) + .show() + } + + private fun saveTPMSConfig(newConfig: Map) { + val commands = mutableListOf() + for ((key, value) in newConfig) { + if (tpmsConfig[key] != value) { + commands.add("7,config set xsq $key $value") + } + } + + if (commands.isEmpty()) return + + // Send commands one by one + for (cmd in commands) { + sendCommand("Saving...", cmd, null) + } + + // Give it a short delay and reload + view?.postDelayed({ loadTPMSConfig() }, 1500) } private fun updateServiceInfo(carData: CarData?) { // Show known car service interval info: val serviceBtn= findViewById(R.id.serviceToggle) as ExtendedFloatingActionButton val serviceTextView = findViewById(R.id.serviceinfo) as TextView + val serviceCard = findViewById(R.id.cardServiceInfo) as View? + + val vehicleId = carData?.sel_vehicleid - var serviceInfo = "" + val serviceInfoParts = mutableListOf() if (carData!!.car_servicerange >= 0) { - serviceInfo += String.format("%d km", carData.car_servicerange) + serviceInfoParts.add(String.format("%d km", carData.car_servicerange)) } if (carData.car_servicetime >= 0) { - if (serviceInfo != "") { - serviceInfo += "\n" - } val now = System.currentTimeMillis() / 1000 val serviceDays = (carData.car_servicetime - now) / 86400 - var daystring = getString(R.string.ndays) - serviceInfo += String.format(daystring, serviceDays) + + var timeInfo = String.format(getString(R.string.ndays), serviceDays) + if (serviceDays > 0) { + val date = Date(carData.car_servicetime.toLong() * 1000) + val dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM) + timeInfo += " (${dateFormat.format(date)})" + } + serviceInfoParts.add(timeInfo) } if (carData.car_gen_substate != "" && carData.car_type == "SQ") { - var servicelvl = carData.car_gen_substate - serviceInfo += "\n" - serviceInfo += String.format(" %s", servicelvl) + serviceInfoParts.add(carData.car_gen_substate) } - if (serviceInfo == "") { + + if (serviceInfoParts.isEmpty()) { serviceBtn.visibility = View.INVISIBLE serviceBtn.isEnabled = false - serviceTextView.visibility = View.INVISIBLE + serviceCard?.visibility = View.GONE } else { serviceBtn.visibility = View.VISIBLE - serviceTextView.text = serviceInfo + // Join parts with a bullet or separator to keep them side by side + serviceTextView.text = serviceInfoParts.joinToString(" • ") + + // Apply persisted visibility + val showService = appPrefs.getData("pref_controls_show_service_$vehicleId", "0") == "1" + serviceCard?.isVisible = showService } serviceBtn.setOnClickListener { // Toggle visibility - serviceTextView.visibility = - if (serviceTextView.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE + serviceCard?.let { card -> + card.isVisible = !card.isVisible + appPrefs.saveData("pref_controls_show_service_$vehicleId", if (card.isVisible) "1" else "0") + } } if(carData.car_type == "SQ") { @@ -149,9 +340,16 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { val flTPMS = findViewById(R.id.tpmsFL) as TextView val rrTPMS = findViewById(R.id.tpmsRR) as TextView val rlTPMS = findViewById(R.id.tpmsRL) as TextView + val cardFL = findViewById(R.id.cardTPMS_FL) as View? + val cardFR = findViewById(R.id.cardTPMS_FR) as View? + val cardRL = findViewById(R.id.cardTPMS_RL) as View? + val cardRR = findViewById(R.id.cardTPMS_RR) as View? + val cardConfig = findViewById(R.id.cardTPMSConfig) as View? val staleTPMS = findViewById(R.id.tpmsStale) as TextView val tpmsFAB = findViewById(R.id.tpmsToggle) as ExtendedFloatingActionButton + val vehicleId = getLastSelectedCarId() + // Disable TPMS for vehicles not supporting any: if (carData?.car_type in listOf("RT", "EN", "NRJK")) { tpmsFAB.isEnabled = false @@ -159,17 +357,29 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { return } + // Apply persisted visibility + val showTPMS = appPrefs.getData("pref_controls_show_tpms_$vehicleId", "0") == "1" + val initialVis = if (showTPMS) View.VISIBLE else View.GONE + cardFL?.visibility = initialVis + cardFR?.visibility = initialVis + cardRL?.visibility = initialVis + cardRR?.visibility = initialVis + staleTPMS.visibility = initialVis + if (carData?.car_type == "SQ" && !tpmsConfig.isEmpty()) { + cardConfig?.visibility = initialVis + } + tpmsFAB.setOnClickListener { - frTPMS.visibility = - if (frTPMS.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE - flTPMS.visibility = - if (flTPMS.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE - rrTPMS.visibility = - if (rrTPMS.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE - rlTPMS.visibility = - if (rlTPMS.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE - staleTPMS.visibility = - if (staleTPMS.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE + val nextVis = if (cardFR?.visibility == View.VISIBLE) View.GONE else View.VISIBLE + cardFL?.visibility = nextVis + cardFR?.visibility = nextVis + cardRL?.visibility = nextVis + cardRR?.visibility = nextVis + staleTPMS.visibility = nextVis + if (carData?.car_type == "SQ" && !tpmsConfig.isEmpty()) { + cardConfig?.visibility = nextVis + } + appPrefs.saveData("pref_controls_show_tpms_$vehicleId", if (nextVis == View.VISIBLE) "1" else "0") } // Long click to open TPMS details via CommandActivity @@ -184,8 +394,6 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { true } - val vehicleId = getLastSelectedCarId() - // Check if new TPMS data (msg code 'Y') is available val hasNewTPMS = carData?.car_tpms_wheelname != null && carData.car_tpms_wheelname!!.isNotEmpty() @@ -396,8 +604,12 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { rrTPMS.text = buildTPMSDisplay(3) // Apply alert colors - val alertColors = intArrayOf(Color.WHITE, Color.YELLOW, Color.RED) - val defaultColor = context?.resources?.getColor(R.color.colorAccent) ?: Color.WHITE + val alertColors = intArrayOf( + MaterialColors.getColor(requireContext(), android.R.attr.textColorPrimary, Color.BLACK), + ContextCompat.getColor(requireContext(), R.color.chargeOtherColor), + ContextCompat.getColor(requireContext(), R.color.colorTextError) + ) + val defaultColor = ContextCompat.getColor(requireContext(), R.color.colorAccent) flTPMS.setTextColor(if (getAlertLevel(0) > 0) alertColors[getAlertLevel(0)] else defaultColor) frTPMS.setTextColor(if (getAlertLevel(1) > 0) alertColors[getAlertLevel(1)] else defaultColor) @@ -414,17 +626,20 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { else -> DataStale.NoValue } + val errorColor = ContextCompat.getColor(requireContext(), R.color.colorTextError) + val warningColor = ContextCompat.getColor(requireContext(), R.color.chargeOtherColor) + when (stale1) { DataStale.NoValue -> { - staleTPMS.setTextColor(Color.RED) + staleTPMS.setTextColor(errorColor) staleTPMS.text = getString(R.string.no_data).lowercase() } DataStale.Stale -> { - staleTPMS.setTextColor(Color.YELLOW) + staleTPMS.setTextColor(warningColor) staleTPMS.text = getString(R.string.stale_data).lowercase() } DataStale.Good -> { - context?.resources?.getColor(R.color.colorAccent)?.let { staleTPMS.setTextColor(it) } + staleTPMS.setTextColor(defaultColor) staleTPMS.text = getString(R.string.latest_data).lowercase() } } @@ -437,7 +652,7 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { val days = minutes / (60 * 24) if (minutes > 0L) { - staleTPMS.setTextColor(Color.YELLOW) + staleTPMS.setTextColor(warningColor) val periodText = when { minutes == 1L -> getText(R.string.min1).toString() days > 1 -> String.format(getText(R.string.ndays).toString(), days) @@ -449,7 +664,9 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { // Auto-expand TPMS display if any alert is active if ((0..3).any { getAlertLevel(it) > 0 }) { - tpmsFAB.toggle() + if (cardFR?.visibility != View.VISIBLE) { + tpmsFAB.performClick() + } } } @@ -511,15 +728,6 @@ class ControlsFragment : BaseFragment(), OnResultCommandListener { centerActionsAdapter.notifyDataSetChanged() } - override fun update(carData: CarData?) { - this.carData = carData - updateTPMSData(carData) - sideActionsAdapter.setCarData(carData) - centerActionsAdapter.setCarData(carData) - sideActionsAdapter.notifyDataSetChanged() - centerActionsAdapter.notifyDataSetChanged() - initialiseCarRendering(carData) - } override fun onResultCommand(result: Array) { if (result.size <= 1) return diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/EnergyFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/EnergyFragment.kt index 5682d78f..715f2963 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/EnergyFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/EnergyFragment.kt @@ -76,7 +76,8 @@ class EnergyFragment : BaseFragment(), OnResultCommandListener, EnergyMetricsAda var socBattLayers = emptyList().toMutableList() val socBackground = ContextCompat.getDrawable(requireContext(), R.drawable.ic_batt_l0) - socBackground!!.setTint(Color.GRAY) + val greyBlue = ContextCompat.getColor(requireContext(), R.color.colorGreyBlue) + socBackground!!.setTint(greyBlue) socBattLayers += socBackground // Get icon scaling offsets in display density: @@ -104,7 +105,7 @@ class EnergyFragment : BaseFragment(), OnResultCommandListener, EnergyMetricsAda } else if (soc <= 20) { layer1Drawable.setTint(Color.YELLOW) } else { - layer1Drawable.setTint(Color.WHITE) + layer1Drawable.setTint(ContextCompat.getColor(requireContext(), R.color.colorDarkgreen)) } socBattLayers += layer1Drawable } @@ -112,6 +113,7 @@ class EnergyFragment : BaseFragment(), OnResultCommandListener, EnergyMetricsAda val socBattLayer = LayerDrawable(socBattLayers.toTypedArray()) socBattIcon.setImageDrawable(socBattLayer) socTextTitle.text = getString(R.string.battery_data_soc_2) + socTextTitle.setTextColor(greyBlue) socText.text = carData?.car_soc var showSoc = true socText.setOnClickListener { @@ -120,6 +122,9 @@ class EnergyFragment : BaseFragment(), OnResultCommandListener, EnergyMetricsAda socTextTitle.text = if (!showSoc) getString(R.string.EstimatedShort) else getString(R.string.battery_data_soc_2) } + // Battery SOH + battSohTitle.setTextColor(greyBlue) + // Battery temp val batteryTemp = findViewById(R.id.battTemp) as TextView diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/HomeFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/HomeFragment.kt index bcbd0fdf..2e48e447 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/HomeFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/HomeFragment.kt @@ -1,6 +1,9 @@ package com.openvehicles.OVMS.ui2.pages +import android.animation.ObjectAnimator +import android.animation.ValueAnimator import android.content.Context +import android.content.res.ColorStateList import android.content.DialogInterface import android.content.pm.PackageManager import android.graphics.Bitmap @@ -105,6 +108,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.launch import android.location.Address import androidx.appcompat.view.ActionMode +import com.google.android.material.color.MaterialColors import com.openvehicles.OVMS.utils.Base64 /** @@ -123,6 +127,8 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It private lateinit var quickActionsEditorAdapter: QuickActionsEditorAdapter private lateinit var tabsAdapter: HomeTabsAdapter private var textColor by Delegates.notNull() + private var batteryAnimator: ObjectAnimator? = null + private var chargingCardAnimator: ValueAnimator? = null private lateinit var appPrefs: AppPrefs @@ -206,6 +212,10 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It } override fun onDestroyView() { + batteryAnimator?.cancel() + batteryAnimator = null + chargingCardAnimator?.cancel() + chargingCardAnimator = null // Ensure we remove any pending callbacks to avoid leaks when view is destroyed view?.findViewById(R.id.carStatus)?.let { statusView -> parkTimeRunnable?.let { statusView.removeCallbacks(it) } @@ -402,6 +412,8 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It initialiseTabs(carData) initialiseBottomInfo(carData) initialiseCarDropDown() + gsmicon(carData) + gpsicon(carData) // Provide color provider to tabs adapter tabsAdapter.setColorProvider { tab -> @@ -541,6 +553,9 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It View.inflate(context, android.R.layout.simple_spinner_item, null) as TextView textView.setText(getItem(position)?.carName) textView.textSize = 20F + // Use theme-aware text color + val color = MaterialColors.getColor(textView, android.R.attr.textColorPrimary) + textView.setTextColor(color) return textView } @@ -556,7 +571,14 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It row = convertView } row?.findViewById(R.id.txt_title)?.text = getItem(position)?.carName - row?.findViewById(R.id.img_car)?.setImageResource(getDrawableIdentifier(context, getItem(position)?.carData?.sel_vehicle_image)) + val carData = getItem(position)?.carData + val imgCar = row?.findViewById(R.id.img_car) + val customDrawable = Ui.getCarDrawable(context, carData?.sel_vehicle_image) + if (customDrawable != null) { + imgCar?.setImageDrawable(customDrawable) + } else { + imgCar?.setImageResource(getDrawableIdentifier(context, carData?.sel_vehicle_image)) + } row?.findViewById(R.id.menuRange)?.text = String.format("%s, %s: %s, %s: %s", getItem(position)?.carData?.car_soc, context.getString(R.string.IdealShort).split(" ").first(), getItem(position)?.carData?.car_range_ideal, context.getString(R.string.EstimatedShort).split(" ").first(), getItem(position)?.carData?.car_range_estimated) return row!! } @@ -580,8 +602,9 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It (activity as MainActivityUI2?)?.supportActionBar?.title = carData?.sel_vehicle_label // SOC icon and label - val socText: TextView = findViewById(R.id.battPercent) as TextView - val rangeText: TextView = findViewById(R.id.battRange) as TextView + val socValueText: TextView = findViewById(R.id.battPercentValue) as TextView + val rangeValueText: TextView = findViewById(R.id.battRangeValue) as TextView + val rangeUnitText: TextView = findViewById(R.id.battRangeUnit) as TextView val socBattIcon = findViewById(R.id.batteryIndicatorView) as ImageView var socBattLayers = emptyList().toMutableList() @@ -615,41 +638,88 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It } else if (soc <= 20) { layer1Drawable.setTint(Color.YELLOW) } else { - layer1Drawable.setTint(Color.WHITE) + layer1Drawable.setTint(ContextCompat.getColor(requireContext(), R.color.colorDarkgreen)) } socBattLayers += layer1Drawable } val socBattLayer = LayerDrawable(socBattLayers.toTypedArray()) socBattIcon.setImageDrawable(socBattLayer) - socText.text = when (socState) { - 1 -> getString(R.string.ideal_range_abbreviation, carData?.car_range_ideal) - 2 -> getString(R.string.estimated_range_abbreviation, carData?.car_range_estimated) - else -> carData?.car_soc + + // Pulsating animation when charging + if (carData?.car_charging == true) { + if (batteryAnimator == null) { + batteryAnimator = ObjectAnimator.ofFloat(socBattIcon, "alpha", 1.0f, 0.4f).apply { + duration = 1500 + repeatCount = ValueAnimator.INFINITE + repeatMode = ValueAnimator.REVERSE + start() + } + } + } else { + batteryAnimator?.cancel() + batteryAnimator = null + socBattIcon.alpha = 1.0f } - val rangeDisplay = appPrefs.appUIPrefs.getStringSet("home_range_display_mode", HashSet()) - val idealRange = rangeDisplay?.contains("ideal") == true - val estimatedRange = rangeDisplay?.contains("estimated") == true + socValueText.text = carData?.car_soc?.replace("%", "") - if (idealRange && estimatedRange) { - rangeText.text = "${getString(R.string.ideal_range_abbreviation, carData?.car_range_ideal)}, ${getString(R.string.estimated_range_abbreviation, carData?.car_range_estimated)}" - } else if (idealRange || estimatedRange) { - rangeText.text = if (idealRange) carData?.car_range_ideal else carData?.car_range_estimated - } else { - val socToggleListener = { - socState += 1 - if (socState > 2) - socState = 0 - socText.text = when (socState) { - 1 -> getString(R.string.ideal_range_abbreviation, carData?.car_range_ideal) - 2 -> getString(R.string.estimated_range_abbreviation, carData?.car_range_estimated) - else -> carData?.car_soc + val socToggleListener = { + socState += 1 + val maxState = if (carData?.car_charging == true) 2 else 1 + if (socState > maxState) + socState = 0 + appPrefs.saveData("home_batt_display_mode", socState.toString()) + update(carData) + } + + // Load persisted mode + socState = appPrefs.getData("home_batt_display_mode", "0")?.toIntOrNull() ?: 0 + if (socState == 2 && carData?.car_charging == false) { + socState = 0 + } + + val unit = carData?.car_distance_units ?: "km" + rangeValueText.text = when (socState) { + 0 -> { + // Show Range(s) based on app settings + val rangeDisplay = appPrefs.appUIPrefs.getStringSet("home_range_display_mode", setOf("ideal", "estimated")) + val showIdeal = rangeDisplay?.contains("ideal") == true + val showEstimated = rangeDisplay?.contains("estimated") == true + + when { + showIdeal && showEstimated -> String.format("%.0f / %.0f", carData?.car_range_ideal_raw ?: 0f, carData?.car_range_estimated_raw ?: 0f) + showIdeal -> String.format("%.0f", carData?.car_range_ideal_raw ?: 0f) + showEstimated -> String.format("%.0f", carData?.car_range_estimated_raw ?: 0f) + else -> String.format("%.0f", carData?.car_range_estimated_raw ?: 0f) } } - socText.setOnClickListener { socToggleListener() } - socBattIcon.setOnClickListener { socToggleListener() } + 1 -> { + // Show Consumption + val used = carData?.car_energyused ?: 0f + val recd = carData?.car_energyrecd ?: 0f + val trip = carData?.car_tripmeter_raw ?: 0f + var consumption = (used - recd) * 1000f / (trip / 10f) + if (!consumption.isFinite()) consumption = 0f + String.format("%.1f", consumption / 10f) + } + 2 -> String.format("%.1f", carData?.car_charge_power_input_kw_raw ?: 0f) + else -> "" } + rangeUnitText.text = when (socState) { + 0 -> unit + 1 -> "kWh/100$unit" + 2 -> "kW" + else -> "" + } + + // Apply click listener to the entire row for easy toggling + findViewById(R.id.heroStatusRow).setOnClickListener { socToggleListener() } + socValueText.setOnClickListener { socToggleListener() } + socBattIcon.setOnClickListener { socToggleListener() } + rangeValueText.setOnClickListener { socToggleListener() } + rangeUnitText.setOnClickListener { socToggleListener() } + // Status label val statusText: TextView = findViewById(R.id.carStatus) as TextView @@ -666,24 +736,29 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It // ONLINE state: differentiate Driving / Charging / Parked val isDriving = carData?.car_started == true val isCharging = carData?.car_charging == true || carData?.car_charge_state_i_raw == 14 + val parkIcon = view?.findViewById(R.id.parkIcon) if (isDriving) { // Ensure any pending parking runnable is removed parkTimeRunnable?.let { statusText.removeCallbacks(it) } parkTimeRunnable = null statusText.text = carData?.car_speed - statusText.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0) + parkIcon?.visibility = View.GONE statusText.setOnClickListener(null) } else if (isCharging) { parkTimeRunnable?.let { statusText.removeCallbacks(it) } parkTimeRunnable = null - statusText.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0) + parkIcon?.visibility = View.GONE // Charging Text is set below } else { // Parked -> apply display mode (immediate toggle on click) applyParkedStatus(statusText, carData) } + // Odometer display + val odoText = view?.findViewById(R.id.carOdometer) + odoText?.text = carData?.car_odometer + if (isCharging) { statusText.setText(R.string.state_charging_label) @@ -773,125 +848,128 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It // Car image val image: ImageView = findViewById(R.id.carStatusImage) as ImageView - val name_splitted = carData?.sel_vehicle_image?.split("_") - val car_tire_image1 = getDrawableIdentifier( - context, - name_splitted?.minus(name_splitted.last())?.joinToString("_") +"_tireanim" - ) - var layers = emptyList() + val customDrawable = Ui.getCarDrawable(requireContext(), carData?.sel_vehicle_image) - if (carData?.car_rearleftdoor_open == true || CAR_RENDER_TEST_MODE_D) { - val modeResource = getDrawableIdentifier( + if (carData?.sel_vehicle_image?.startsWith("file://") == true && customDrawable != null) { + layers = layers.plus(customDrawable) + } else { + val name_splitted = carData?.sel_vehicle_image?.split("_") + val car_tire_image1 = getDrawableIdentifier( context, - carData?.sel_vehicle_image+"_rld" + name_splitted?.minus(name_splitted.last())?.joinToString("_") + "_tireanim" ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - } - if (carData?.car_frontleftdoor_open == true || CAR_RENDER_TEST_MODE_D) { - val modeResource = getDrawableIdentifier( - context, - carData?.sel_vehicle_image+"_fld" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - } - - layers = layers.plus(ContextCompat.getDrawable(requireContext(), getDrawableIdentifier( - context, - carData?.sel_vehicle_image - ) - )!!) - - val speedShownInUI = car_tire_image1 > 0 && (carData?.car_started == true||CAR_RENDER_TEST_MODE_D) - - if (speedShownInUI) { - val animationDrawable = context?.getDrawable(car_tire_image1) as AnimationDrawable - val carAnim = CarAnimationDrawable() - var drawables = emptyList() - for (i in 0.. 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) } - drawables.forEach { carAnim.addFrame(it, 35) } - carAnim.isOneShot = false - layers = layers.plus(carAnim) + if (carData?.car_frontleftdoor_open == true || CAR_RENDER_TEST_MODE_D) { + val modeResource = getDrawableIdentifier( + context, + carData?.sel_vehicle_image + "_fld" + ) + if (modeResource > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + } + val carResId = getDrawableIdentifier(context, carData?.sel_vehicle_image) + if (carResId > 0) { + layers = layers.plus(ContextCompat.getDrawable(requireContext(), carResId)!!) + } + val speedShownInUI = + car_tire_image1 > 0 && (carData?.car_started == true || CAR_RENDER_TEST_MODE_D) - if ((carData?.car_speed_raw ?: 0f) > 0) { - // Adjust animaion speed - carAnim.start() - carAnim.setDuration((320 / (carData?.car_speed_raw!!/3.5)).toInt()) + if (speedShownInUI) { + val animationDrawable = + AppCompatResources.getDrawable(requireContext(), car_tire_image1) as AnimationDrawable + val carAnim = CarAnimationDrawable() + var drawables = emptyList() + for (i in 0 until animationDrawable.numberOfFrames) { + drawables = drawables.plus(animationDrawable.getFrame(i)) + } + drawables.forEach { carAnim.addFrame(it, 35) } + carAnim.isOneShot = false + layers = layers.plus(carAnim) + + if ((carData?.car_speed_raw ?: 0f) > 0) { + // Adjust animation speed + carAnim.start() + carAnim.setDuration((320 / (carData?.car_speed_raw!! / 3.5)).toInt()) + } } - } - - if (carData?.car_headlights_on == true || CAR_RENDER_TEST_MODE_HD) { - val modeResource = getDrawableIdentifier( - context, - name_splitted?.minus(name_splitted.last())?.joinToString("_") +"_hd" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - } + if (carData?.car_headlights_on == true || CAR_RENDER_TEST_MODE_HD) { + val modeResource = getDrawableIdentifier( + context, + name_splitted?.minus(name_splitted.last())?.joinToString("_") + "_hd" + ) + if (modeResource > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + } - if (carData?.car_rearrightdoor_open == true || CAR_RENDER_TEST_MODE_D) { - val modeResource = getDrawableIdentifier( - context, - carData?.sel_vehicle_image+"_rrd" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - } + if (carData?.car_rearrightdoor_open == true || CAR_RENDER_TEST_MODE_D) { + val modeResource = getDrawableIdentifier( + context, + carData?.sel_vehicle_image + "_rrd" + ) + if (modeResource > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + } - if (carData?.car_frontrightdoor_open == true || CAR_RENDER_TEST_MODE_D) { - val modeResource = getDrawableIdentifier( - context, - carData?.sel_vehicle_image+"_frd" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - } + if (carData?.car_frontrightdoor_open == true || CAR_RENDER_TEST_MODE_D) { + val modeResource = getDrawableIdentifier( + context, + carData?.sel_vehicle_image + "_frd" + ) + if (modeResource > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + } - if (carData?.car_trunk_open == true || CAR_RENDER_TEST_MODE_T) { - val modeResource = getDrawableIdentifier( - context, - carData?.sel_vehicle_image+"_t" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - else { + if (carData?.car_trunk_open == true || CAR_RENDER_TEST_MODE_T) { val modeResource = getDrawableIdentifier( context, - name_splitted?.minus(name_splitted.last())?.joinToString("_")+"_t" + carData?.sel_vehicle_image + "_t" ) if (modeResource > 0) layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + else { + val modeRes = getDrawableIdentifier( + context, + name_splitted?.minus(name_splitted.last())?.joinToString("_") + "_t" + ) + if (modeRes > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeRes)!!) + } } - } - if (carData?.car_bonnet_open == true || CAR_RENDER_TEST_MODE_T) { - val modeResource = getDrawableIdentifier( - context, - carData?.sel_vehicle_image+"_b" - ) - if (modeResource > 0) - layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) - else { + if (carData?.car_bonnet_open == true || CAR_RENDER_TEST_MODE_T) { val modeResource = getDrawableIdentifier( context, - name_splitted?.minus(name_splitted.last())?.joinToString("_")+"_b" + carData?.sel_vehicle_image + "_b" ) if (modeResource > 0) layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) + else { + val modeRes = getDrawableIdentifier( + context, + name_splitted?.minus(name_splitted.last())?.joinToString("_") + "_b" + ) + if (modeRes > 0) + layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeRes)!!) + } } } - var chargingResName = name_splitted?.minus(name_splitted.last())?.joinToString("_") + val name_splitted = carData?.sel_vehicle_image?.split("_") + var chargingResName = name_splitted?.minus(name_splitted?.last() ?: "")?.joinToString("_") if (carData?.car_charge_mode == "performance" || (carData?.car_charge_current_raw ?: 0f) > 48) { chargingResName += "_q" @@ -928,6 +1006,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It } + /* Deactivated for artifact diagnosis if (carData?.car_charge_state_i_raw == 0x01 || carData?.car_charge_state_i_raw == 0x02 || carData?.car_charge_state_i_raw == 0x0f || carData?.car_charging == true || CAR_RENDER_TEST_MODE_CHG) { val modeResource = getDrawableIdentifier( context, @@ -936,6 +1015,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It if (modeResource > 0) layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) } + */ val newDrawable = LayerDrawable(layers.toTypedArray()) @@ -949,39 +1029,32 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It override fun onAnimationRepeat(animation: Animation) {} override fun onAnimationEnd(animation: Animation) {} }) - image.startAnimation(anim_in) - lastPresentCarId = carData?.sel_vehicleid - } else if (speedShownInUI) { - image.setImageDrawable( - newDrawable - ) - } + // image.startAnimation(anim_in) + lastPresentCarId = carData?.sel_vehicleid + } else { + image.setImageDrawable( + newDrawable + ) } +} + // Helper to show parked status & toggle on click between simple label and duration display private fun applyParkedStatus(statusText: TextView, carData: CarData?) { val showDuration = appPrefs.getData("pref_show_parktime", "0") == "1" + val parkIcon = view?.findViewById(R.id.parkIcon) - // Remove any previous runnable if present - parkTimeRunnable?.let { statusText.removeCallbacks(it) } + // Remove any previous runnable if present + parkTimeRunnable?.let { statusText.removeCallbacks(it) } if (!showDuration) { // Show plain "parked" label only statusText.setText(R.string.parked) - statusText.setCompoundDrawablesWithIntrinsicBounds(0,0,0,0) - statusText.compoundDrawablePadding = 0 + parkIcon?.visibility = View.GONE parkTimeRunnable = null } else { // Show icon + duration - val icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_p_framed) - statusText.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) - // Space between icon and text = width of two spaces - try { - val twoSpacesWidth = statusText.paint.measureText(" ") // two spaces - statusText.compoundDrawablePadding = twoSpacesWidth.toInt() - } catch (_: Exception) { - statusText.compoundDrawablePadding = (statusText.resources.displayMetrics.density * 8).toInt() // Fallback ~8dp - } + parkIcon?.visibility = VISIBLE // Server-provided elapsed parking time in seconds (authoritative) -> convert to ms val baseElapsedMs = (carData?.car_parking_timer_raw ?: 0L) @@ -1035,6 +1108,33 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It val chargePortOpen = if (carData?.car_type == "NL") (carData.car_charging || carData.car_charge_timer) else carData?.car_chargeport_open == true chargingCard.visibility = if (chargePortOpen == true) VISIBLE else View.GONE + val flowIndicator = findViewById(R.id.chargingFlowIndicator) as View? + flowIndicator?.visibility = if (carData?.car_charging == true) VISIBLE else View.GONE + + // Glow effect for charging card + if (carData?.car_charging == true) { + chargingCard.strokeColor = ContextCompat.getColor(requireContext(), R.color.colorAccent) + if (chargingCardAnimator == null) { + chargingCardAnimator = ValueAnimator.ofFloat(0.5f, 4f).apply { + duration = 1200 + repeatCount = ValueAnimator.INFINITE + repeatMode = ValueAnimator.REVERSE + addUpdateListener { animator -> + chargingCard.strokeWidth = TypedValue.applyDimension( + COMPLEX_UNIT_DIP, + animator.animatedValue as Float, + resources.displayMetrics + ).toInt() + } + start() + } + } + } else { + chargingCardAnimator?.cancel() + chargingCardAnimator = null + chargingCard.strokeWidth = 0 + } + val ampLimitSlider = findViewById(R.id.seekBar) as RangeSlider if ((carData?.car_charge_currentlimit_raw ?: 0f) > 31f) { @@ -1685,7 +1785,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It if (carData?.car_gsmlock?.isNotEmpty() == true) { if (baseInfoBuilder.isNotEmpty()) baseInfoBuilder.append("\n") - baseInfoBuilder.append("GSM: ${carData.car_gsmlock} ${carData.car_mdm_mode}") + baseInfoBuilder.append("Network: ${carData.car_gsmlock} \nMode: ${carData.car_mdm_mode} ${carData.car_gsm_signal}") } if (carData?.car_vin?.isNotEmpty() == true) { @@ -1705,23 +1805,34 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It private fun gsmicon(carData: CarData?) { // GSM Bar + val gsmimg = findViewById(R.id.gsmView) as ImageView if (appPrefs.getData("gsm_icon", "off") == "on") { - val gsmimg = findViewById(R.id.gsmView) as ImageView gsmimg.visibility = VISIBLE - gsmimg.setImageResource( - getDrawableIdentifier( - activity, - "signal_strength_" + carData?.car_gsm_bars - ) + val resId = getDrawableIdentifier( + requireContext(), + "signal_strength_" + (carData?.car_gsm_bars ?: 0) ) + if (resId > 0) { + gsmimg.setImageResource(resId) + } else { + gsmimg.setImageResource(R.drawable.signal_strength_0) + } + // Use static tint from resources to be safe + gsmimg.imageTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.colorText)) + } else { + gsmimg.visibility = View.GONE } } private fun gpsicon(carData: CarData?) { // GPS Bar + val gpsimg = findViewById(R.id.gpsView) as ImageView if (appPrefs.getData("gps_icon", "off") == "on") { - val gpsimg = findViewById(R.id.gpsView) as ImageView - gpsimg.visibility = if(carData?.car_gpslock == true) VISIBLE else View.INVISIBLE + gpsimg.visibility = VISIBLE + gpsimg.alpha = if(carData?.car_gpslock == true) 1.0f else 0.3f + gpsimg.imageTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.colorText)) + } else { + gpsimg.visibility = View.GONE } } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/AppUISettingsFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/AppUISettingsFragment.kt index 5c8f5cf9..f9c29036 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/AppUISettingsFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/AppUISettingsFragment.kt @@ -1,5 +1,7 @@ package com.openvehicles.OVMS.ui2.pages.settings +import com.openvehicles.OVMS.ui2.misc.ThemeMode +import androidx.preference.ListPreference import android.content.DialogInterface import android.content.Intent import android.os.Bundle @@ -15,6 +17,7 @@ import com.openvehicles.OVMS.api.ApiService import com.openvehicles.OVMS.ui.utils.Ui import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.entities.CarData +import com.openvehicles.OVMS.ui.MainActivity import com.openvehicles.OVMS.utils.CarsStorage import com.openvehicles.OVMS.utils.Sys import com.openvehicles.OVMS.utils.CarsStorage.getLastSelectedCarId @@ -43,6 +46,18 @@ class AppUISettingsFragment: PreferenceFragmentCompat() { carData = CarsStorage.getSelectedCarData() val appPrefs = AppPrefs(requireContext(), "ovms") + // Light / dark / follow system. Stored in the "ovms" prefs by ThemeMode, so the + // ListPreference (which writes to the default prefs) is mirrored across and the + // new mode is applied right away - the Activity recreates itself. + findPreference(ThemeMode.PREF_KEY)?.let { pref -> + pref.value = ThemeMode.read(requireContext()) + pref.onPreferenceChangeListener = + OnPreferenceChangeListener { _, newValue -> + ThemeMode.applyAndStore(requireContext(), newValue as String) + true + } + } + val home_range_display_mode_preference = findPreference("home_range_display_mode") home_range_display_mode_preference?.setSummaryFromValues(home_range_display_mode_preference.values.toTypedArray()) home_range_display_mode_preference?.onPreferenceChangeListener = object : OnPreferenceChangeListener { @@ -171,7 +186,14 @@ class AppUISettingsFragment: PreferenceFragmentCompat() { oldUIPreference?.isChecked = appPrefs.getData("option_oldui_enabled", "0") == "1" oldUIPreference?.onPreferenceChangeListener = OnPreferenceChangeListener { preference, newValue -> - appPrefs.saveData("option_oldui_enabled", if (newValue as Boolean) "1" else "0") + val enabled = newValue as Boolean + appPrefs.saveData("option_oldui_enabled", if (enabled) "1" else "0") + if (enabled) { + // Switch to old UI immediately: + val intent = Intent(requireContext(), MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + startActivity(intent) + } true } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/CarEditorFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/CarEditorFragment.kt index 82a0be17..e19b804a 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/CarEditorFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/CarEditorFragment.kt @@ -16,6 +16,9 @@ import android.widget.FrameLayout import android.widget.Gallery import android.widget.ImageView import androidx.appcompat.app.AlertDialog +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider +import androidx.lifecycle.Lifecycle import androidx.navigation.fragment.findNavController import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textfield.MaterialAutoCompleteTextView @@ -34,7 +37,11 @@ import com.openvehicles.OVMS.utils.AppPrefs import com.openvehicles.OVMS.utils.CarsStorage.getSelectedCarData import com.openvehicles.OVMS.utils.CarsStorage.getStoredCars import com.openvehicles.OVMS.utils.CarsStorage.saveStoredCars - +import androidx.activity.result.contract.ActivityResultContracts +import com.google.android.material.button.MaterialButton +import java.io.File +import java.io.FileOutputStream +import com.openvehicles.OVMS.ui.utils.Ui.getCarDrawable class CarEditorFragment : BaseFragment() { @@ -48,6 +55,128 @@ class CarEditorFragment : BaseFragment() { private lateinit var gcmSenders: Array private var server: EditText? = null private var gcmSender: EditText? = null + + private var customImagePath: String? = null + private var btnCustomImage: MaterialButton? = null + private var btnClearCustomImage: MaterialButton? = null + private var imgCustomPreview: ImageView? = null + + private var customImagePathOl: String? = null + private var btnCustomImageOl: MaterialButton? = null + private var btnClearCustomImageOl: MaterialButton? = null + private var imgCustomPreviewOl: ImageView? = null + private var galleryCarOl: Gallery? = null + + private var customImagePathMap: String? = null + private var btnCustomImageMap: MaterialButton? = null + private var btnClearCustomImageMap: MaterialButton? = null + private var imgCustomPreviewMap: ImageView? = null + private var galleryCarMap: Gallery? = null + + private val pickImageLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + uri?.let { + try { + val inputStream = requireContext().contentResolver.openInputStream(it) + val fileName = "car_custom_${System.currentTimeMillis()}.png" + val file = File(requireContext().filesDir, fileName) + val outputStream = FileOutputStream(file) + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + + customImagePath = "file://" + file.absolutePath + updateCustomImageUI() + Log.d(TAG, "PickImage: saved to $customImagePath") + } catch (e: Exception) { + Log.e(TAG, "PickImage failed", e) + } + } + } + + private val pickOlImageLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + uri?.let { + try { + val inputStream = requireContext().contentResolver.openInputStream(it) + val fileName = "car_custom_ol_${System.currentTimeMillis()}.png" + val file = File(requireContext().filesDir, fileName) + val outputStream = FileOutputStream(file) + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + + customImagePathOl = "file://" + file.absolutePath + updateCustomOlImageUI() + Log.d(TAG, "PickOlImage: saved to $customImagePathOl") + } catch (e: Exception) { + Log.e(TAG, "PickOlImage failed", e) + } + } + } + + private val pickMapImageLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + uri?.let { + try { + val inputStream = requireContext().contentResolver.openInputStream(it) + val fileName = "car_custom_map_${System.currentTimeMillis()}.png" + val file = File(requireContext().filesDir, fileName) + val outputStream = FileOutputStream(file) + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + + customImagePathMap = "file://" + file.absolutePath + updateCustomMapImageUI() + Log.d(TAG, "PickMapImage: saved to $customImagePathMap") + } catch (e: Exception) { + Log.e(TAG, "PickMapImage failed", e) + } + } + } + + private fun updateCustomImageUI() { + if (customImagePath != null) { + btnClearCustomImage?.visibility = View.VISIBLE + imgCustomPreview?.visibility = View.VISIBLE + imgCustomPreview?.setImageDrawable(getCarDrawable(requireContext(), customImagePath)) + galleryCar?.alpha = 0.3f + galleryCar?.isEnabled = false + } else { + btnClearCustomImage?.visibility = View.GONE + imgCustomPreview?.visibility = View.GONE + galleryCar?.alpha = 1.0f + galleryCar?.isEnabled = true + } + } + + private fun updateCustomOlImageUI() { + if (customImagePathOl != null) { + btnClearCustomImageOl?.visibility = View.VISIBLE + imgCustomPreviewOl?.visibility = View.VISIBLE + imgCustomPreviewOl?.setImageDrawable(getCarDrawable(requireContext(), customImagePathOl)) + galleryCarOl?.alpha = 0.3f + galleryCarOl?.isEnabled = false + } else { + btnClearCustomImageOl?.visibility = View.GONE + imgCustomPreviewOl?.visibility = View.GONE + galleryCarOl?.alpha = 1.0f + galleryCarOl?.isEnabled = true + } + } + + private fun updateCustomMapImageUI() { + if (customImagePathMap != null) { + btnClearCustomImageMap?.visibility = View.VISIBLE + imgCustomPreviewMap?.visibility = View.VISIBLE + imgCustomPreviewMap?.setImageDrawable(getCarDrawable(requireContext(), customImagePathMap)) + galleryCarMap?.alpha = 0.3f + galleryCarMap?.isEnabled = false + } else { + btnClearCustomImageMap?.visibility = View.GONE + imgCustomPreviewMap?.visibility = View.GONE + galleryCarMap?.alpha = 1.0f + galleryCarMap?.isEnabled = true + } + } override fun onCreateView( inflater: LayoutInflater, @@ -57,8 +186,8 @@ class CarEditorFragment : BaseFragment() { return inflater.inflate(R.layout.fragment_careditor_v2, null) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) editPosition = requireArguments().getInt("position", -1) if (editPosition >= 0) { @@ -90,34 +219,81 @@ class CarEditorFragment : BaseFragment() { selectServer!!.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id -> setSelectedServer(position, true) } galleryCar = requireView().findViewById(R.id.ga_car) as Gallery - galleryCar!!.setAdapter(CarImgAdapter()) - setHasOptionsMenu(true) - load() - } + galleryCar!!.setAdapter(CarImgAdapter(availableColors)) - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.control_save_delete, menu) - } + btnCustomImage = requireView().findViewById(R.id.btn_custom_image) + btnClearCustomImage = requireView().findViewById(R.id.btn_clear_custom_image) + imgCustomPreview = requireView().findViewById(R.id.img_custom_preview) - override fun onPrepareOptionsMenu(menu: Menu) { - Log.d("CarEditorFragment", "onPrepareOptionsMenu edit car: " + (getStoredCars().size > 1)) - menu.findItem(R.id.mi_delete).setVisible(carData != null && getStoredCars().size > 1) - // hide control, not necessary - menu.findItem(R.id.mi_control).setVisible(false) - } + btnCustomImage?.setOnClickListener { + pickImageLauncher.launch("image/*") + } + + btnClearCustomImage?.setOnClickListener { + customImagePath = null + updateCustomImageUI() + } + + galleryCarOl = requireView().findViewById(R.id.ga_car_ol) as Gallery + galleryCarOl!!.setAdapter(CarImgAdapter(availableOlImages, true)) - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.mi_save -> { - save() - true + btnCustomImageOl = requireView().findViewById(R.id.btn_custom_image_ol) + btnClearCustomImageOl = requireView().findViewById(R.id.btn_clear_custom_image_ol) + imgCustomPreviewOl = requireView().findViewById(R.id.img_custom_preview_ol) + + btnCustomImageOl?.setOnClickListener { + pickOlImageLauncher.launch("image/*") + } + + btnClearCustomImageOl?.setOnClickListener { + customImagePathOl = null + updateCustomOlImageUI() + } + + galleryCarMap = requireView().findViewById(R.id.ga_car_map) as Gallery + galleryCarMap!!.setAdapter(CarImgAdapter(availableMapIcons, false)) + + btnCustomImageMap = requireView().findViewById(R.id.btn_custom_image_map) + btnClearCustomImageMap = requireView().findViewById(R.id.btn_clear_custom_image_map) + imgCustomPreviewMap = requireView().findViewById(R.id.img_custom_preview_map) + + btnCustomImageMap?.setOnClickListener { + pickMapImageLauncher.launch("image/*") + } + + btnClearCustomImageMap?.setOnClickListener { + customImagePathMap = null + updateCustomMapImageUI() + } + + val menuHost: MenuHost = requireActivity() + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.control_save_delete, menu) } - R.id.mi_delete -> { - delete() - true + + override fun onPrepareMenu(menu: Menu) { + Log.d("CarEditorFragment", "onPrepareMenu edit car: " + (getStoredCars().size > 1)) + menu.findItem(R.id.mi_delete).setVisible(carData != null && getStoredCars().size > 1) + menu.findItem(R.id.mi_control).setVisible(false) } - else -> false - } + + override fun onMenuItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.mi_save -> { + save() + true + } + R.id.mi_delete -> { + delete() + true + } + else -> false + } + } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) + + load() } private fun delete() { @@ -187,7 +363,24 @@ class CarEditorFragment : BaseFragment() { carData!!.sel_gcm_senderid = getValue(rootView, R.id.txt_gcm_senderid) carData!!.sel_tls = (rootView.findViewById(R.id.chk_tls_enabled) as SwitchMaterial).isChecked carData!!.sel_tls_trust_all = (rootView.findViewById(R.id.chk_tls_trust_all) as SwitchMaterial).isChecked - carData!!.sel_vehicle_image = availableColors[galleryCar!!.selectedItemPosition] + + if (customImagePath != null) { + carData!!.sel_vehicle_image = customImagePath!! + } else { + carData!!.sel_vehicle_image = availableColors[galleryCar!!.selectedItemPosition] + } + + if (customImagePathOl != null) { + carData!!.sel_vehicle_image_ol = customImagePathOl!! + } else { + carData!!.sel_vehicle_image_ol = availableOlImages[galleryCarOl!!.selectedItemPosition] + } + + if (customImagePathMap != null) { + carData!!.sel_vehicle_image_map = customImagePathMap!! + } else { + carData!!.sel_vehicle_image_map = availableMapIcons[galleryCarMap!!.selectedItemPosition] + } } catch (e: ValidationException) { Log.e("Validation", e.message, e) return @@ -240,15 +433,62 @@ class CarEditorFragment : BaseFragment() { } // set car image: - var index = -1 - for (imgRes in availableColors) { - index++ - if (imgRes == carData!!.sel_vehicle_image) { - break + if (carData!!.sel_vehicle_image.startsWith("file://")) { + customImagePath = carData!!.sel_vehicle_image + updateCustomImageUI() + } else { + customImagePath = null + updateCustomImageUI() + var index = -1 + for (imgRes in availableColors) { + index++ + if (imgRes == carData!!.sel_vehicle_image) { + break + } + } + if (index >= 0) { + galleryCar!!.setSelection(index) + } + } + + // set top-down image: + if (carData!!.sel_vehicle_image_ol.isNotEmpty() && carData!!.sel_vehicle_image_ol.startsWith("file://")) { + customImagePathOl = carData!!.sel_vehicle_image_ol + updateCustomOlImageUI() + } else { + customImagePathOl = null + updateCustomOlImageUI() + var index = -1 + val targetImg = if (carData!!.sel_vehicle_image_ol.isEmpty()) carData!!.sel_vehicle_image else carData!!.sel_vehicle_image_ol + for (imgRes in availableOlImages) { + index++ + if (imgRes == targetImg) { + break + } + } + if (index >= 0) { + galleryCarOl!!.setSelection(index) } } - if (index >= 0) { - galleryCar!!.setSelection(index) + + // set map image: + if (!carData!!.sel_vehicle_image_map.isNullOrEmpty() && carData!!.sel_vehicle_image_map.startsWith("file://")) { + customImagePathMap = carData!!.sel_vehicle_image_map + updateCustomMapImageUI() + } else { + customImagePathMap = null + updateCustomMapImageUI() + var index = -1 + val targetImg = if (carData!!.sel_vehicle_image_map.isNullOrEmpty()) "map_car_default" else carData!!.sel_vehicle_image_map + for (imgRes in availableMapIcons) { + index++ + if (imgRes == targetImg) { + break + } + } + if (index >= 0) { + galleryCarMap!!.setSelection(index) + } } // save selected vehicle label: @@ -296,6 +536,64 @@ class CarEditorFragment : BaseFragment() { private const val TAG = "CarEditorFragment" + private val availableMapIcons = arrayOf( + "map_car_default", + "map_car_ampera_black", + "map_car_i3", + "map_car_imiev", + "map_car_ioniq5_cybergray", + "map_car_ioniq_polarwhite", + "map_car_kangoo", + "map_car_kiaev6_white", + "map_car_kianiro_grey", + "map_car_leaf2_gunmetallic", + "map_car_leaf_pearlwhite", + "map_car_mgzs_white", + "map_car_niu_mqi_gt_white", + "map_car_nrjk", + "map_car_smart", + "map_car_thinkcity_classicblack", + "map_car_twizy_snowwhitewithblack", + "map_car_vwup_white", + "map_car_zoe_white" + ) + + private val availableOlImages = arrayOf( + "car_roadster_arcticwhite", + "car_roadster_brilliantyellow", + "car_roadster_electricblue", + "car_roadster_fushionred", + "car_roadster_glacierblue", + "car_roadster_jetblack", + "car_roadster_lightninggreen", + "car_roadster_obsidianblack", + "car_roadster_racinggreen", + "car_roadster_radiantred", + "car_roadster_sterlingsilver", + "car_roadster_thundergray", + "car_roadster_twilightblue", + "car_roadster_veryorange", + "car_ampera", + "car_i3", + "car_imiev", + "car_ioniq5_cybergray", + "car_ioniq_polarwhite", + "car_kangoo", + "car_kiaev6_white", + "car_kianiro_grey", + "car_leaf2_pearlwhite", + "car_leaf_pearlwhite", + "car_mgzs_white", + "car_niu_mqi_gt", + "car_nrjk", + "car_nrjkexperia", + "car_smart", + "car_thinkcity_classicblack", + "car_twizy", + "car_vwup_white", + "car_zoe_white" + ) + private val availableColors = arrayOf( "car_roadster_arcticwhite", "car_roadster_brilliantyellow", @@ -427,14 +725,14 @@ class CarEditorFragment : BaseFragment() { ) } - private class CarImgAdapter : BaseAdapter() { + private class CarImgAdapter(private val images: Array, private val isOl: Boolean = false) : BaseAdapter() { override fun getCount(): Int { - return availableColors.size + return images.size } override fun getItem(position: Int): Any { - return availableColors[position] + return images[position] } override fun getItemId(position: Int): Long { @@ -443,9 +741,15 @@ class CarEditorFragment : BaseFragment() { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val iv = convertView as? ImageView ?: ImageView(parent.context) - iv.setScaleType(ImageView.ScaleType.FIT_START) + val density = parent.context.resources.displayMetrics.density + iv.layoutParams = Gallery.LayoutParams((200 * density).toInt(), (120 * density).toInt()) + iv.setScaleType(ImageView.ScaleType.CENTER_INSIDE) iv.setAdjustViewBounds(true) - iv.setImageResource(getDrawableIdentifier(parent.context, availableColors[position])) + val p = (8 * density).toInt() + iv.setPadding(p, p, p, p) + iv.setBackgroundResource(R.drawable.gallery_item_selector) + val resName = if (isOl) "ol_" + images[position] else images[position] + iv.setImageResource(getDrawableIdentifier(parent.context, resName)) return iv } } diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/SettingsFragment.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/SettingsFragment.kt index 8e057f37..eeac3d70 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/SettingsFragment.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/pages/settings/SettingsFragment.kt @@ -13,13 +13,19 @@ import android.widget.AdapterView.OnItemClickListener import android.widget.BaseAdapter import android.widget.ImageButton import android.widget.ImageView +import android.widget.LinearLayout import android.widget.ListView import android.widget.TextView +import androidx.core.view.MenuHost +import com.google.android.material.button.MaterialButton +import androidx.core.view.MenuProvider +import androidx.lifecycle.Lifecycle import androidx.navigation.fragment.findNavController import com.openvehicles.OVMS.R import com.openvehicles.OVMS.api.ApiService import com.openvehicles.OVMS.entities.CarData import com.openvehicles.OVMS.ui.BaseFragment +import com.openvehicles.OVMS.ui.utils.Ui import com.openvehicles.OVMS.ui.utils.Ui.getDrawableIdentifier import com.openvehicles.OVMS.utils.CarsStorage.getStoredCars @@ -32,33 +38,40 @@ class SettingsFragment : BaseFragment(), OnItemClickListener { container: ViewGroup?, savedInstanceState: Bundle? ): View { - listView = ListView(container!!.context) - listView!!.fitsSystemWindows = true - return listView + val root = inflater.inflate(R.layout.fragment_settings_v2, container, false) + listView = root.findViewById(R.id.listView) + val appSettingsBtn = root.findViewById(R.id.appSettingsBtn) + appSettingsBtn.setOnClickListener { showGlobalOptions() } + return root } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE) listView.onItemClickListener = this listView.setAdapter(SettingsAdapter(activity, getStoredCars())) - setHasOptionsMenu(true) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.settings_options, menu) - } + val menuHost: MenuHost = requireActivity() + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.settings_options, menu) + } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.mi_add) { - edit(-1) - return true - } else if (item.itemId == R.id.mi_globaloptions) { - showGlobalOptions() - return true - } - return super.onOptionsItemSelected(item) + override fun onMenuItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.mi_add -> { + edit(-1) + true + } + R.id.mi_globaloptions -> { + showGlobalOptions() + true + } + else -> false + } + } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) } override fun update(carData: CarData?) { @@ -170,12 +183,17 @@ class SettingsFragment : BaseFragment(), OnItemClickListener { btnInfo.tag = position val it = items!![position] var iv = view.findViewById(R.id.img_car) as ImageView - iv.setImageResource( - getDrawableIdentifier( - parent.context, - it.sel_vehicle_image + val customDrawable = Ui.getCarDrawable(parent.context, it.sel_vehicle_image) + if (customDrawable != null) { + iv.setImageDrawable(customDrawable) + } else { + iv.setImageResource( + getDrawableIdentifier( + parent.context, + it.sel_vehicle_image + ) ) - ) + } (view.findViewById(R.id.txt_title) as TextView).text = it.sel_vehicle_label if (listView == null && parent is ListView) { diff --git a/app/src/main/java/com/openvehicles/OVMS/ui2/rendering/CarRenderingUtils.kt b/app/src/main/java/com/openvehicles/OVMS/ui2/rendering/CarRenderingUtils.kt index 9a106fff..43b9103a 100644 --- a/app/src/main/java/com/openvehicles/OVMS/ui2/rendering/CarRenderingUtils.kt +++ b/app/src/main/java/com/openvehicles/OVMS/ui2/rendering/CarRenderingUtils.kt @@ -17,29 +17,34 @@ object CarRenderingUtils { fun getTopDownCarLayers(carData: CarData, context: Context, climate: Boolean = false, heat: Boolean = false): List { var layers = emptyList() + val overlayImage = if (carData.sel_vehicle_image_ol.isNotEmpty()) carData.sel_vehicle_image_ol else carData.sel_vehicle_image + val customDrawable = Ui.getCarDrawable(context, overlayImage) + if (overlayImage.startsWith("file://") && customDrawable != null) { + return listOf(customDrawable) + } - var overlayResource: String = carData.sel_vehicle_image + var overlayResource: String = overlayImage - val name_splitted = carData.sel_vehicle_image.split("_"); + val name_splitted = overlayImage.split("_"); when { - carData.sel_vehicle_image.startsWith("car_imiev_") - || carData.sel_vehicle_image.startsWith("car_i3_") - || carData.sel_vehicle_image.startsWith("car_ampera_") - || carData.sel_vehicle_image.startsWith("car_twizy_") - || carData.sel_vehicle_image.startsWith("car_kangoo_") -> { + overlayImage.startsWith("car_imiev_") + || overlayImage.startsWith("car_i3_") + || overlayImage.startsWith("car_ampera_") + || overlayImage.startsWith("car_twizy_") + || overlayImage.startsWith("car_kangoo_") -> { // one ol image for all colors: overlayResource = name_splitted.minus(name_splitted.last()) .joinToString("_") } - carData.sel_vehicle_image.startsWith("car_holdenvolt_") -> overlayResource = "car_ampera" // Holdenvolt: one ol image for all colors (same as ampera) - carData.sel_vehicle_image.startsWith("car_kianiro_") -> overlayResource = "car_kianiro_grey" - carData.sel_vehicle_image.startsWith("car_smart_44") -> overlayResource = "car_vwup_silver" // Smart forfour share a single image - carData.sel_vehicle_image.startsWith("car_smart_") -> overlayResource = "car_smart" // Smart fortwo share a single image - carData.sel_vehicle_image.startsWith("car_nrjkexperia") -> overlayResource = "car_nrjkexperia" - carData.sel_vehicle_image.startsWith("car_nrjk") -> overlayResource = "car_nrjk" - carData.sel_vehicle_image.startsWith("car_niu_mqi_gt") -> overlayResource = "car_niu_mqi_gt" + overlayImage.startsWith("car_holdenvolt_") -> overlayResource = "car_ampera" // Holdenvolt: one ol image for all colors (same as ampera) + overlayImage.startsWith("car_kianiro_") -> overlayResource = "car_kianiro_grey" + overlayImage.startsWith("car_smart_44") -> overlayResource = "car_vwup_silver" // Smart forfour share a single image + overlayImage.startsWith("car_smart_") -> overlayResource = "car_smart" // Smart fortwo share a single image + overlayImage.startsWith("car_nrjkexperia") -> overlayResource = "car_nrjkexperia" + overlayImage.startsWith("car_nrjk") -> overlayResource = "car_nrjk" + overlayImage.startsWith("car_niu_mqi_gt") -> overlayResource = "car_niu_mqi_gt" } var otherResName = overlayResource.split("_").minus(name_splitted.last()) diff --git a/app/src/main/res/color/control_normal.xml b/app/src/main/res/color/control_normal.xml new file mode 100644 index 00000000..45d234b4 --- /dev/null +++ b/app/src/main/res/color/control_normal.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_bookmark_add.xml b/app/src/main/res/drawable-anydpi/ic_action_bookmark_add.xml index 63bfe036..95bcaaa1 100644 --- a/app/src/main/res/drawable-anydpi/ic_action_bookmark_add.xml +++ b/app/src/main/res/drawable-anydpi/ic_action_bookmark_add.xml @@ -3,9 +3,9 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="#FFFFFF" + android:tint="@color/colorText" android:alpha="0.8"> diff --git a/app/src/main/res/drawable-anydpi/ic_action_pin.xml b/app/src/main/res/drawable-anydpi/ic_action_pin.xml index 7c7bb78e..b323b515 100644 --- a/app/src/main/res/drawable-anydpi/ic_action_pin.xml +++ b/app/src/main/res/drawable-anydpi/ic_action_pin.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="#FFFFFF" + android:tint="@color/colorText" android:alpha="0.8"> diff --git a/app/src/main/res/drawable-anydpi/ic_action_send.xml b/app/src/main/res/drawable-anydpi/ic_action_send.xml index ddc07d00..6528b6d0 100644 --- a/app/src/main/res/drawable-anydpi/ic_action_send.xml +++ b/app/src/main/res/drawable-anydpi/ic_action_send.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="#FFFFFF" + android:tint="@color/colorText" android:alpha="0.8" android:autoMirrored="true"> diff --git a/app/src/main/res/drawable-anydpi/ic_arrow_forward.xml b/app/src/main/res/drawable-anydpi/ic_arrow_forward.xml index f5e03a10..62f9c72a 100644 --- a/app/src/main/res/drawable-anydpi/ic_arrow_forward.xml +++ b/app/src/main/res/drawable-anydpi/ic_arrow_forward.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="#FFFFFF" + android:tint="@color/colorText" android:alpha="0.8" android:autoMirrored="true"> diff --git a/app/src/main/res/drawable-anydpi/ic_bookmarks.xml b/app/src/main/res/drawable-anydpi/ic_bookmarks.xml index 84792b70..44068b33 100644 --- a/app/src/main/res/drawable-anydpi/ic_bookmarks.xml +++ b/app/src/main/res/drawable-anydpi/ic_bookmarks.xml @@ -3,9 +3,9 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="#FFFFFF" + android:tint="@color/colorText" android:alpha="0.8"> diff --git a/app/src/main/res/drawable/gallery_item_selector.xml b/app/src/main/res/drawable/gallery_item_selector.xml new file mode 100644 index 00000000..4bcc294b --- /dev/null +++ b/app/src/main/res/drawable/gallery_item_selector.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ac.xml b/app/src/main/res/drawable/ic_ac.xml index e520bcf9..55be1579 100644 --- a/app/src/main/res/drawable/ic_ac.xml +++ b/app/src/main/res/drawable/ic_ac.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_action_about.xml b/app/src/main/res/drawable/ic_action_about.xml index b0161393..1d7065c3 100644 --- a/app/src/main/res/drawable/ic_action_about.xml +++ b/app/src/main/res/drawable/ic_action_about.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_action_control.xml b/app/src/main/res/drawable/ic_action_control.xml index 5a8f5a8e..c83cf635 100644 --- a/app/src/main/res/drawable/ic_action_control.xml +++ b/app/src/main/res/drawable/ic_action_control.xml @@ -1,6 +1,6 @@ - - - + + diff --git a/app/src/main/res/drawable/ic_action_discard.xml b/app/src/main/res/drawable/ic_action_discard.xml index 282594cd..248451f7 100644 --- a/app/src/main/res/drawable/ic_action_discard.xml +++ b/app/src/main/res/drawable/ic_action_discard.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_action_edit.xml b/app/src/main/res/drawable/ic_action_edit.xml index 59b6c849..cf9c3a8a 100644 --- a/app/src/main/res/drawable/ic_action_edit.xml +++ b/app/src/main/res/drawable/ic_action_edit.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_action_new.xml b/app/src/main/res/drawable/ic_action_new.xml index 37c166cb..3a71e9ba 100644 --- a/app/src/main/res/drawable/ic_action_new.xml +++ b/app/src/main/res/drawable/ic_action_new.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_action_save.xml b/app/src/main/res/drawable/ic_action_save.xml index e70f136c..569120e3 100644 --- a/app/src/main/res/drawable/ic_action_save.xml +++ b/app/src/main/res/drawable/ic_action_save.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_action_settings.xml b/app/src/main/res/drawable/ic_action_settings.xml index a54c82c2..aa5ecf08 100644 --- a/app/src/main/res/drawable/ic_action_settings.xml +++ b/app/src/main/res/drawable/ic_action_settings.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_car_sleep.xml b/app/src/main/res/drawable/ic_car_sleep.xml index 5d77fef8..2b5127f9 100644 --- a/app/src/main/res/drawable/ic_car_sleep.xml +++ b/app/src/main/res/drawable/ic_car_sleep.xml @@ -5,5 +5,5 @@ android:viewportHeight="56"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_charger.xml b/app/src/main/res/drawable/ic_charger.xml index 2dd701f6..e440f4e7 100644 --- a/app/src/main/res/drawable/ic_charger.xml +++ b/app/src/main/res/drawable/ic_charger.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml index 829fbb8e..74615ef9 100644 --- a/app/src/main/res/drawable/ic_chat.xml +++ b/app/src/main/res/drawable/ic_chat.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_climate_schedule.xml b/app/src/main/res/drawable/ic_climate_schedule.xml index 42eb00fb..925cd11f 100644 --- a/app/src/main/res/drawable/ic_climate_schedule.xml +++ b/app/src/main/res/drawable/ic_climate_schedule.xml @@ -5,13 +5,13 @@ android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_controls_tab.xml b/app/src/main/res/drawable/ic_controls_tab.xml index a9216200..1bd42b13 100644 --- a/app/src/main/res/drawable/ic_controls_tab.xml +++ b/app/src/main/res/drawable/ic_controls_tab.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_custom_command.xml b/app/src/main/res/drawable/ic_custom_command.xml index c19864bb..77180557 100644 --- a/app/src/main/res/drawable/ic_custom_command.xml +++ b/app/src/main/res/drawable/ic_custom_command.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_energy.xml b/app/src/main/res/drawable/ic_energy.xml index 3a3ef9e9..cf767dd3 100644 --- a/app/src/main/res/drawable/ic_energy.xml +++ b/app/src/main/res/drawable/ic_energy.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_health.xml b/app/src/main/res/drawable/ic_health.xml index e0de8731..61ca3bc2 100644 --- a/app/src/main/res/drawable/ic_health.xml +++ b/app/src/main/res/drawable/ic_health.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 00000000..984a5216 --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_homelink.xml b/app/src/main/res/drawable/ic_homelink.xml index 088aea5b..f43891bb 100644 --- a/app/src/main/res/drawable/ic_homelink.xml +++ b/app/src/main/res/drawable/ic_homelink.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_homelink_num.xml b/app/src/main/res/drawable/ic_homelink_num.xml index 38f37b2d..f2886c18 100644 --- a/app/src/main/res/drawable/ic_homelink_num.xml +++ b/app/src/main/res/drawable/ic_homelink_num.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_lock_closed.xml b/app/src/main/res/drawable/ic_lock_closed.xml index ead35ff0..b2888fc1 100644 --- a/app/src/main/res/drawable/ic_lock_closed.xml +++ b/app/src/main/res/drawable/ic_lock_closed.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_lock_open.xml b/app/src/main/res/drawable/ic_lock_open.xml index 17162317..5afca52d 100644 --- a/app/src/main/res/drawable/ic_lock_open.xml +++ b/app/src/main/res/drawable/ic_lock_open.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml new file mode 100644 index 00000000..2eff65ef --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_my_location.xml b/app/src/main/res/drawable/ic_my_location.xml new file mode 100644 index 00000000..982e003f --- /dev/null +++ b/app/src/main/res/drawable/ic_my_location.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_navigation.xml b/app/src/main/res/drawable/ic_navigation.xml index b4faa861..be72ecd3 100644 --- a/app/src/main/res/drawable/ic_navigation.xml +++ b/app/src/main/res/drawable/ic_navigation.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_no1.xml b/app/src/main/res/drawable/ic_no1.xml index 8cdddbd3..5bb07612 100644 --- a/app/src/main/res/drawable/ic_no1.xml +++ b/app/src/main/res/drawable/ic_no1.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_no2.xml b/app/src/main/res/drawable/ic_no2.xml index 26945b55..7e976e4c 100644 --- a/app/src/main/res/drawable/ic_no2.xml +++ b/app/src/main/res/drawable/ic_no2.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_no3.xml b/app/src/main/res/drawable/ic_no3.xml index 66e049c0..2bb7cfa9 100644 --- a/app/src/main/res/drawable/ic_no3.xml +++ b/app/src/main/res/drawable/ic_no3.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_notification_icon.xml b/app/src/main/res/drawable/ic_notification_icon.xml index 2d74f18a..2c085dd7 100644 --- a/app/src/main/res/drawable/ic_notification_icon.xml +++ b/app/src/main/res/drawable/ic_notification_icon.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_odometer.xml b/app/src/main/res/drawable/ic_odometer.xml new file mode 100644 index 00000000..58aa38e8 --- /dev/null +++ b/app/src/main/res/drawable/ic_odometer.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000..66baeba0 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_remote_control.xml b/app/src/main/res/drawable/ic_remote_control.xml index 21ff68da..f1958e2a 100644 --- a/app/src/main/res/drawable/ic_remote_control.xml +++ b/app/src/main/res/drawable/ic_remote_control.xml @@ -27,7 +27,7 @@ android:pathData="m67.11,53.91 l-27.96,0.02c0,-0 -6.4,-7.44 -6.47,-7.89l41.48,-0.03zM33.28,92.9 L32.74,63.43 39.38,63.43 39.89,86.06 66.37,86.04 67.13,63.41 73.76,63.4 73.03,92.87 33.28,92.9" android:strokeAlpha="0.85045" android:strokeWidth="0.72" - android:fillColor="#ffffff" + android:fillColor="@color/colorText" android:strokeColor="#00000000" android:fillType="nonZero" android:fillAlpha="0.85045"/> diff --git a/app/src/main/res/drawable/ic_satellite.xml b/app/src/main/res/drawable/ic_satellite.xml index f995c5c6..96a0a7a0 100644 --- a/app/src/main/res/drawable/ic_satellite.xml +++ b/app/src/main/res/drawable/ic_satellite.xml @@ -4,6 +4,6 @@ android:viewportWidth="591.49" android:viewportHeight="591.59"> diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index a54c82c2..aa5ecf08 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/ic_tabs_menu.xml b/app/src/main/res/drawable/ic_tabs_menu.xml index d34a5c0e..afdee1f4 100644 --- a/app/src/main/res/drawable/ic_tabs_menu.xml +++ b/app/src/main/res/drawable/ic_tabs_menu.xml @@ -6,6 +6,6 @@ android:viewportHeight="24"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_temp.xml b/app/src/main/res/drawable/ic_temp.xml index 54da5a27..cce01cfb 100644 --- a/app/src/main/res/drawable/ic_temp.xml +++ b/app/src/main/res/drawable/ic_temp.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/ic_zoom_in.xml b/app/src/main/res/drawable/ic_zoom_in.xml new file mode 100644 index 00000000..671efd60 --- /dev/null +++ b/app/src/main/res/drawable/ic_zoom_in.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/signal_strength_0.xml b/app/src/main/res/drawable/signal_strength_0.xml index b3c9b72c..d5c7490d 100644 --- a/app/src/main/res/drawable/signal_strength_0.xml +++ b/app/src/main/res/drawable/signal_strength_0.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/signal_strength_1.xml b/app/src/main/res/drawable/signal_strength_1.xml index 70febf4c..90e83a51 100644 --- a/app/src/main/res/drawable/signal_strength_1.xml +++ b/app/src/main/res/drawable/signal_strength_1.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/signal_strength_2.xml b/app/src/main/res/drawable/signal_strength_2.xml index 423f201d..2f8e88e7 100644 --- a/app/src/main/res/drawable/signal_strength_2.xml +++ b/app/src/main/res/drawable/signal_strength_2.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/signal_strength_3.xml b/app/src/main/res/drawable/signal_strength_3.xml index f17f9c48..62cefe7a 100644 --- a/app/src/main/res/drawable/signal_strength_3.xml +++ b/app/src/main/res/drawable/signal_strength_3.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/signal_strength_4.xml b/app/src/main/res/drawable/signal_strength_4.xml index ee8471f8..cfef59b9 100644 --- a/app/src/main/res/drawable/signal_strength_4.xml +++ b/app/src/main/res/drawable/signal_strength_4.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/signal_strength_5.xml b/app/src/main/res/drawable/signal_strength_5.xml index a44d73fa..6654cdcc 100644 --- a/app/src/main/res/drawable/signal_strength_5.xml +++ b/app/src/main/res/drawable/signal_strength_5.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:fillColor="@color/colorText"/> diff --git a/app/src/main/res/drawable/topview_ac_arrows.xml b/app/src/main/res/drawable/topview_ac_arrows.xml index f9598dc7..565a7ce0 100644 --- a/app/src/main/res/drawable/topview_ac_arrows.xml +++ b/app/src/main/res/drawable/topview_ac_arrows.xml @@ -10,7 +10,7 @@ + android:fillColor="@color/colorText" /> + android:fillColor="@color/colorText" /> + android:fillColor="@color/colorText" /> diff --git a/app/src/main/res/layout/activity_main2.xml b/app/src/main/res/layout/activity_main2.xml index f19fbd80..6066a50d 100644 --- a/app/src/main/res/layout/activity_main2.xml +++ b/app/src/main/res/layout/activity_main2.xml @@ -15,27 +15,25 @@ + android:layout_height="?attr/actionBarSize"> - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/charging_card.xml b/app/src/main/res/layout/charging_card.xml index e6afe12b..b85b51ef 100644 --- a/app/src/main/res/layout/charging_card.xml +++ b/app/src/main/res/layout/charging_card.xml @@ -7,15 +7,28 @@ android:layout_margin="3dp" android:padding="8dp"> + + - + @@ -42,7 +42,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Information" - android:textColor="@android:color/white" + android:textColor="@color/colorText" android:textSize="20sp" android:textStyle="bold" /> diff --git a/app/src/main/res/layout/dialog_edit_tpms_config.xml b/app/src/main/res/layout/dialog_edit_tpms_config.xml new file mode 100644 index 00000000..0aad630c --- /dev/null +++ b/app/src/main/res/layout/dialog_edit_tpms_config.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dlg_stored_command.xml b/app/src/main/res/layout/dlg_stored_command.xml index 8fbdbeb5..1d2af36a 100755 --- a/app/src/main/res/layout/dlg_stored_command.xml +++ b/app/src/main/res/layout/dlg_stored_command.xml @@ -15,6 +15,8 @@ android:autofillHints="" android:hint="@string/lb_enter_title" android:inputType="text" + android:textColor="@color/colorText" + android:textColorHint="@color/colorGreyBlue" android:textAppearance="?android:attr/textAppearanceMedium" android:textStyle="bold" /> @@ -26,9 +28,12 @@ android:fontFamily="monospace" android:hint="@string/lb_enter_command" android:inputType="text|textNoSuggestions" + android:textColor="@color/colorText" + android:textColorHint="@color/colorGreyBlue" android:maxLines="1" android:minLines="1" android:textAppearance="?android:attr/textAppearanceSmall" android:typeface="monospace" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/energy_metric.xml b/app/src/main/res/layout/energy_metric.xml index b6146a97..b8445303 100644 --- a/app/src/main/res/layout/energy_metric.xml +++ b/app/src/main/res/layout/energy_metric.xml @@ -1,33 +1,29 @@ - + android:padding="12dp"> - + - + - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_car.xml b/app/src/main/res/layout/fragment_car.xml index 6a661524..08650a36 100644 --- a/app/src/main/res/layout/fragment_car.xml +++ b/app/src/main/res/layout/fragment_car.xml @@ -26,37 +26,37 @@ android:id="@+id/tabCarImageCarTrunkOpen" style="@style/TabCarImgCar" android:src="@drawable/roadster_outline_tr" - android:visibility="invisible" /> + android:visibility="gone" /> + android:visibility="gone" /> + android:visibility="gone" /> + android:visibility="gone" /> + android:visibility="gone" /> + android:visibility="gone" /> + android:visibility="gone" /> @@ -306,7 +306,7 @@ android:gravity="center" android:singleLine="false" android:text="▴999.9 ▾99.9 kWh" - android:textColor="@color/colorForeground" + android:textColor="@color/colorText" android:textSize="22px" android:typeface="monospace" /> @@ -321,7 +321,7 @@ android:gravity="center" android:singleLine="false" android:text="➟1234.5km" - android:textColor="@color/colorForeground" + android:textColor="@color/colorText" android:textSize="22px" android:typeface="monospace" /> @@ -338,7 +338,7 @@ android:phoneNumber="true" android:singleLine="false" android:text="123.4kph" - android:textColor="@color/colorForeground" + android:textColor="@color/colorText" android:textSize="32px" android:textStyle="bold" android:typeface="monospace" /> diff --git a/app/src/main/res/layout/fragment_careditor_v2.xml b/app/src/main/res/layout/fragment_careditor_v2.xml index 5a3a8b4d..de5e80f9 100755 --- a/app/src/main/res/layout/fragment_careditor_v2.xml +++ b/app/src/main/res/layout/fragment_careditor_v2.xml @@ -144,18 +144,162 @@ android:layout_marginStart="20dp" android:text="@string/lb_tls_trust_all" /> + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_charging.xml b/app/src/main/res/layout/fragment_charging.xml index 64af9c68..552623fb 100644 --- a/app/src/main/res/layout/fragment_charging.xml +++ b/app/src/main/res/layout/fragment_charging.xml @@ -11,7 +11,7 @@ android:id="@+id/chargingProgressBar" android:layout_width="0dp" android:layout_height="wrap_content" - android:visibility="invisible" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -22,7 +22,7 @@ android:layout_height="wrap_content" android:elevation="3dp" android:indeterminate="true" - android:visibility="invisible" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -64,6 +64,7 @@ android:layout_height="wrap_content" android:textSize="16sp" android:textStyle="bold" + android:textColor="@color/colorText" android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/battSoc" app:layout_constraintEnd_toEndOf="@+id/battIndicatorImg" @@ -77,6 +78,7 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:gravity="center" + android:textColor="@color/colorText" app:layout_constraintEnd_toEndOf="@+id/battIndicatorImg" app:layout_constraintStart_toStartOf="@+id/battIndicatorImg" app:layout_constraintTop_toBottomOf="@+id/battIndicatorImg" /> @@ -88,6 +90,7 @@ android:layout_marginStart="16dp" android:gravity="center" android:textSize="24sp" + android:textColor="@color/colorText" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/battIndicatorImg" app:layout_constraintStart_toStartOf="@+id/battIndicatorImg" @@ -99,6 +102,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" + android:textColor="@color/colorText" app:layout_constraintBottom_toBottomOf="@+id/battVolt" app:layout_constraintEnd_toStartOf="@+id/battVolt" app:layout_constraintHorizontal_bias="0.0" @@ -112,6 +116,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:textSize="16sp" + android:textColor="@color/colorText" app:layout_constraintBottom_toBottomOf="@+id/battTemp" app:layout_constraintEnd_toEndOf="@+id/battIndicatorImg" app:layout_constraintTop_toTopOf="@+id/battTemp" @@ -123,6 +128,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="24dp" android:textSize="16sp" + android:textColor="@color/colorText" app:layout_constraintEnd_toStartOf="@+id/battAmp" app:layout_constraintTop_toTopOf="@+id/battAmp" tools:text="370V" /> @@ -133,6 +139,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:textSize="16sp" + android:textColor="@color/colorText" app:layout_constraintBottom_toBottomOf="@+id/battIndicatorImg" app:layout_constraintEnd_toEndOf="@+id/battIndicatorImg" app:layout_constraintStart_toStartOf="@+id/battIndicatorImg" @@ -144,6 +151,7 @@ android:layout_height="wrap_content" android:layout_marginStart="24dp" android:textSize="16sp" + android:textColor="@color/colorText" app:layout_constraintBottom_toBottomOf="@+id/battVolt" app:layout_constraintStart_toEndOf="@+id/battAmp" app:layout_constraintTop_toTopOf="@+id/battVolt" @@ -192,6 +200,10 @@ android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginEnd="20dp" + app:cardCornerRadius="16dp" + app:strokeWidth="1dp" + app:strokeColor="@color/colorGreyBlue" + app:cardBackgroundColor="?attr/colorSurfaceContainer" app:contentPadding="12dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -266,6 +278,10 @@ android:layout_marginStart="20dp" android:layout_marginTop="16dp" android:layout_marginEnd="20dp" + app:cardCornerRadius="16dp" + app:strokeWidth="1dp" + app:strokeColor="@color/colorGreyBlue" + app:cardBackgroundColor="?attr/colorSurfaceContainer" app:contentPadding="12dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -291,6 +307,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="6dp" + android:textColor="@color/colorText" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -359,6 +376,10 @@ android:layout_marginStart="20dp" android:layout_marginTop="16dp" android:layout_marginEnd="20dp" + app:cardCornerRadius="16dp" + app:strokeWidth="1dp" + app:strokeColor="@color/colorGreyBlue" + app:cardBackgroundColor="?attr/colorSurfaceContainer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/chargeModeCard"> @@ -415,6 +436,10 @@ android:layout_marginStart="20dp" android:layout_marginTop="16dp" android:layout_marginEnd="20dp" + app:cardCornerRadius="16dp" + app:strokeWidth="1dp" + app:strokeColor="@color/colorGreyBlue" + app:cardBackgroundColor="?attr/colorSurfaceContainer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/socLimitCard"> @@ -472,6 +497,10 @@ android:layout_marginTop="16dp" android:layout_marginEnd="20dp" android:layout_marginBottom="32dp" + app:cardCornerRadius="16dp" + app:strokeWidth="1dp" + app:strokeColor="@color/colorGreyBlue" + app:cardBackgroundColor="?attr/colorSurfaceContainer" app:contentPadding="12dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/fragment_controls.xml b/app/src/main/res/layout/fragment_controls.xml index 9f887bbc..dd7154d9 100644 --- a/app/src/main/res/layout/fragment_controls.xml +++ b/app/src/main/res/layout/fragment_controls.xml @@ -1,175 +1,317 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:paddingBottom="32dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +