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 @@ 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 +291,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 { 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..148aed46 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) @@ -99,17 +126,17 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc 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) @@ -123,18 +150,18 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc 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) // @@ -147,17 +174,17 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc 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 +478,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 +524,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 +538,7 @@ class PowerFragment : BaseFragment(), CmdSeries.Listener, ProgressOverlay.OnCanc } xAxis.addLimitLine(l) } - tripChart.legend.textColor = Color.WHITE + tripChart.legend.textColor = colorText tripChart.invalidate() // @@ -543,7 +570,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 +653,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 +664,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 +678,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/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..33e3940b 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) } @@ -541,6 +551,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 } @@ -580,8 +593,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 +629,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 +727,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) @@ -805,28 +871,24 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It ) )!!) - val speedShownInUI = car_tire_image1 > 0 && (carData?.car_started == true||CAR_RENDER_TEST_MODE_D) + 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 animationDrawable = AppCompatResources.getDrawable(requireContext(), car_tire_image1) as AnimationDrawable val carAnim = CarAnimationDrawable() var drawables = emptyList() - for (i in 0.. 0) { - // Adjust animaion speed + // Adjust animation speed carAnim.start() - carAnim.setDuration((320 / (carData?.car_speed_raw!!/3.5)).toInt()) + carAnim.setDuration((320 / (carData?.car_speed_raw!! / 3.5)).toInt()) } - } if (carData?.car_headlights_on == true || CAR_RENDER_TEST_MODE_HD) { @@ -928,6 +990,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 +999,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It if (modeResource > 0) layers = layers.plus(ContextCompat.getDrawable(requireContext(), modeResource)!!) } + */ val newDrawable = LayerDrawable(layers.toTypedArray()) @@ -949,39 +1013,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 +1092,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) { @@ -1714,6 +1798,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It "signal_strength_" + carData?.car_gsm_bars ) ) + gsmimg.imageTintList = ColorStateList.valueOf(textColor) } } @@ -1722,6 +1807,7 @@ class HomeFragment : BaseFragment(), OnResultCommandListener, HomeTabsAdapter.It 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.imageTintList = ColorStateList.valueOf(textColor) } } 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..4132e7ae 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 @@ -57,8 +60,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) { @@ -91,33 +94,35 @@ class CarEditorFragment : BaseFragment() { AdapterView.OnItemClickListener { parent, view, position, id -> setSelectedServer(position, true) } galleryCar = requireView().findViewById(R.id.ga_car) as Gallery galleryCar!!.setAdapter(CarImgAdapter()) - setHasOptionsMenu(true) - load() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.control_save_delete, menu) - } - 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) - } + val menuHost: MenuHost = requireActivity() + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.control_save_delete, menu) + } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.mi_save -> { - save() - 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) } - R.id.mi_delete -> { - delete() - true + + override fun onMenuItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.mi_save -> { + save() + true + } + R.id.mi_delete -> { + delete() + true + } + else -> false + } } - else -> false - } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) + + load() } private fun delete() { 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..45a4acce 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,8 +13,13 @@ 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 @@ -32,33 +37,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?) { 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/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_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"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +