Require confirmation before deleting a system (iOS + Android)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,8 +25,8 @@ android {
|
||||
applicationId = "app.voltplan.cable"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
versionCode = 85
|
||||
versionName = "1.7.0"
|
||||
|
||||
// Aptabase analytics — mirrors the iOS configuration (the iPhone app's tracker).
|
||||
buildConfigField("String", "APTABASE_APP_KEY", "\"A-SH-4260269603\"")
|
||||
|
||||
2
android/app/proguard-rules.pro
vendored
2
android/app/proguard-rules.pro
vendored
@@ -10,3 +10,5 @@
|
||||
# Retrofit
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
# Play Review KTX
|
||||
-dontwarn com.google.android.gms.common.annotation.NoNullnessRewrite
|
||||
|
||||
@@ -5,6 +5,10 @@ import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import app.voltplan.cable.R
|
||||
import app.voltplan.cable.analytics.Analytics
|
||||
import app.voltplan.cable.data.model.effectivePowerWatts
|
||||
@@ -12,6 +16,7 @@ import app.voltplan.cable.data.model.energyWattHours
|
||||
import app.voltplan.cable.data.model.sourceType
|
||||
import app.voltplan.cable.data.UnitSystem
|
||||
import app.voltplan.cable.ui.system.DetailState
|
||||
import app.voltplan.cable.util.Fmt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -25,13 +30,12 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* Fetches the system wiring diagram PNG from the VoltPlan diagram API — the same endpoint and
|
||||
* payload the iOS app uses (`SystemOverviewPDFExporter.fetchDiagramImage`). Used both for the
|
||||
* standalone "Wiring Diagram" image export and the diagram page embedded in the overview PDF.
|
||||
*/
|
||||
object SystemDiagram {
|
||||
private const val ENDPOINT = "https://voltplan.app/api/diagram/generate"
|
||||
private val JSON_MEDIA = "application/json; charset=utf-8".toMediaType()
|
||||
@@ -39,14 +43,29 @@ object SystemDiagram {
|
||||
.callTimeout(15, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
/** Fetches the diagram as a [Bitmap], or null on any network/decoding failure. */
|
||||
// Fallback bitmap dimensions and layout (mirrors iOS drawSystemDiagram)
|
||||
private const val FB_W = 1200
|
||||
private const val FB_H = 750
|
||||
private const val FB_MARGIN = 48f
|
||||
private const val FB_BLOCK_H = 100f
|
||||
private const val FB_BLOCK_SPACING = 14f
|
||||
private const val FB_BLOCK_INSET = 24f
|
||||
private const val FB_MAX_BLOCKS = 4
|
||||
private const val FB_BATT_MIN_H = 260f
|
||||
private val FB_CHARGER_COLOR = Color.rgb(51, 140, 222)
|
||||
private val FB_BATTERY_COLOR = Color.rgb(77, 176, 79)
|
||||
private val FB_LOAD_COLOR = Color.rgb(237, 140, 36)
|
||||
private val FB_CARD_BG = Color.rgb(245, 245, 247)
|
||||
|
||||
/** Fetches the wiring diagram PNG from the VoltPlan API; returns null on any failure. */
|
||||
suspend fun fetch(state: DetailState, unit: UnitSystem): Bitmap? = withContext(Dispatchers.IO) {
|
||||
val payload = buildPayload(state, unit)
|
||||
val body = Json.encodeToString(JsonObject.serializer(), payload).toRequestBody(JSON_MEDIA)
|
||||
val request = Request.Builder()
|
||||
.url(ENDPOINT)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Accept", "image/png")
|
||||
.post(Json.encodeToString(JsonObject.serializer(), payload).toRequestBody(JSON_MEDIA))
|
||||
.post(body)
|
||||
.build()
|
||||
|
||||
runCatching {
|
||||
@@ -57,18 +76,17 @@ object SystemDiagram {
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
/** Fetches the diagram, flattens it onto a white background, and opens the Android share sheet. */
|
||||
/** Always returns a diagram — API result when reachable, local block diagram otherwise. */
|
||||
suspend fun fetchOrFallback(context: Context, state: DetailState, unit: UnitSystem): Bitmap =
|
||||
fetch(state, unit) ?: drawFallback(context, state)
|
||||
|
||||
suspend fun exportAndShare(
|
||||
context: Context,
|
||||
state: DetailState,
|
||||
unit: UnitSystem,
|
||||
onError: () -> Unit,
|
||||
) {
|
||||
val bitmap = fetch(state, unit)
|
||||
if (bitmap == null) {
|
||||
withContext(Dispatchers.Main) { onError() }
|
||||
return
|
||||
}
|
||||
val bitmap = fetchOrFallback(context, state, unit)
|
||||
val file = withContext(Dispatchers.IO) {
|
||||
val opaque = flattenOnWhite(bitmap)
|
||||
val name = state.system?.name?.takeIf { it.isNotBlank() } ?: "System"
|
||||
@@ -83,6 +101,237 @@ object SystemDiagram {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Offline fallback: 3-column block diagram (mirrors iOS drawSystemDiagram)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private fun drawFallback(context: Context, state: DetailState): Bitmap {
|
||||
val bmp = Bitmap.createBitmap(FB_W, FB_H, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bmp)
|
||||
canvas.drawColor(Color.WHITE)
|
||||
|
||||
val colW = (FB_W - FB_MARGIN * 2) / 3f
|
||||
val chargerX = FB_MARGIN
|
||||
val batteryX = FB_MARGIN + colW
|
||||
val loadX = FB_MARGIN + colW * 2f
|
||||
|
||||
// Card background
|
||||
val cardPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = FB_CARD_BG
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
canvas.drawRoundRect(
|
||||
RectF(FB_MARGIN, FB_MARGIN, FB_W - FB_MARGIN, FB_H - FB_MARGIN),
|
||||
24f, 24f, cardPaint,
|
||||
)
|
||||
|
||||
// Column headers
|
||||
val headerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.GRAY
|
||||
textSize = 32f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
val headerY = FB_MARGIN + 44f
|
||||
canvas.drawText(
|
||||
context.getString(R.string.overview_pdf_chargers_section).uppercase(Locale.getDefault()),
|
||||
chargerX + colW / 2f, headerY, headerPaint,
|
||||
)
|
||||
canvas.drawText(
|
||||
context.getString(R.string.battery_bank_header_title).uppercase(Locale.getDefault()),
|
||||
batteryX + colW / 2f, headerY, headerPaint,
|
||||
)
|
||||
canvas.drawText(
|
||||
context.getString(R.string.overview_pdf_loads_section).uppercase(Locale.getDefault()),
|
||||
loadX + colW / 2f, headerY, headerPaint,
|
||||
)
|
||||
|
||||
val contentTop = headerY + 14f
|
||||
|
||||
// Charger blocks
|
||||
val chargersToShow = state.chargers.take(FB_MAX_BLOCKS)
|
||||
val chargerCentersY = mutableListOf<Float>()
|
||||
chargersToShow.forEachIndexed { i, charger ->
|
||||
val y = contentTop + i * (FB_BLOCK_H + FB_BLOCK_SPACING)
|
||||
val rect = RectF(chargerX + FB_BLOCK_INSET, y, chargerX + colW - FB_BLOCK_INSET, y + FB_BLOCK_H)
|
||||
drawComponentBlock(canvas, charger.name, "${Fmt.number(charger.effectivePowerWatts)} W", FB_CHARGER_COLOR, rect)
|
||||
chargerCentersY.add(rect.centerY())
|
||||
}
|
||||
if (state.chargers.isEmpty()) drawDash(canvas, chargerX + colW / 2f, contentTop + 34f)
|
||||
else if (state.chargers.size > FB_MAX_BLOCKS) drawMore(canvas, "+${state.chargers.size - FB_MAX_BLOCKS}", chargerX + colW / 2f, contentTop + FB_MAX_BLOCKS * (FB_BLOCK_H + FB_BLOCK_SPACING) + 26f)
|
||||
|
||||
// Battery block (height matches the taller outer column, minimum 260px)
|
||||
val outerRows = maxOf(chargersToShow.size, minOf(state.loads.size, FB_MAX_BLOCKS)).coerceAtLeast(1)
|
||||
val battH = (outerRows * (FB_BLOCK_H + FB_BLOCK_SPACING) - FB_BLOCK_SPACING).coerceAtLeast(FB_BATT_MIN_H)
|
||||
val battRect = RectF(
|
||||
batteryX + FB_BLOCK_INSET, contentTop,
|
||||
batteryX + colW - FB_BLOCK_INSET, contentTop + battH,
|
||||
)
|
||||
drawBatteryBlock(context, canvas, state, battRect)
|
||||
val batteryCenterY = battRect.centerY()
|
||||
|
||||
// Load blocks
|
||||
val loadsToShow = state.loads.take(FB_MAX_BLOCKS)
|
||||
val loadCentersY = mutableListOf<Float>()
|
||||
loadsToShow.forEachIndexed { i, load ->
|
||||
val y = contentTop + i * (FB_BLOCK_H + FB_BLOCK_SPACING)
|
||||
val rect = RectF(loadX + FB_BLOCK_INSET, y, loadX + colW - FB_BLOCK_INSET, y + FB_BLOCK_H)
|
||||
drawComponentBlock(canvas, load.name, "${Fmt.number(load.power)} W", FB_LOAD_COLOR, rect)
|
||||
loadCentersY.add(rect.centerY())
|
||||
}
|
||||
if (state.loads.isEmpty()) drawDash(canvas, loadX + colW / 2f, contentTop + 34f)
|
||||
else if (state.loads.size > FB_MAX_BLOCKS) drawMore(canvas, "+${state.loads.size - FB_MAX_BLOCKS}", loadX + colW / 2f, contentTop + FB_MAX_BLOCKS * (FB_BLOCK_H + FB_BLOCK_SPACING) + 26f)
|
||||
|
||||
// Arrows: chargers → battery, battery → loads
|
||||
val chargerRight = chargerX + colW - FB_BLOCK_INSET
|
||||
val battLeft = battRect.left
|
||||
val battRight = battRect.right
|
||||
val loadLeft = loadX + FB_BLOCK_INSET
|
||||
for (cy in chargerCentersY) drawArrow(canvas, chargerRight, cy, battLeft, batteryCenterY, FB_CHARGER_COLOR)
|
||||
for (cy in loadCentersY) drawArrow(canvas, battRight, batteryCenterY, loadLeft, cy, FB_LOAD_COLOR)
|
||||
|
||||
return bmp
|
||||
}
|
||||
|
||||
/** White card with colored left accent bar, name (bold), and a detail line. */
|
||||
private fun drawComponentBlock(canvas: Canvas, name: String, detail: String, color: Int, rect: RectF) {
|
||||
val p = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
p.color = Color.WHITE
|
||||
p.style = Paint.Style.FILL
|
||||
canvas.drawRoundRect(rect, 10f, 10f, p)
|
||||
|
||||
p.color = Color.argb(55, 0, 0, 0)
|
||||
p.style = Paint.Style.STROKE
|
||||
p.strokeWidth = 1.5f
|
||||
canvas.drawRoundRect(rect, 10f, 10f, p)
|
||||
|
||||
// Left accent bar (rounded on left, square on right so it sits flush)
|
||||
p.color = color
|
||||
p.style = Paint.Style.FILL
|
||||
canvas.drawRoundRect(RectF(rect.left, rect.top, rect.left + 7f, rect.bottom), 10f, 10f, p)
|
||||
canvas.drawRect(RectF(rect.left + 4f, rect.top, rect.left + 7f, rect.bottom), p)
|
||||
|
||||
val textLeft = rect.left + 16f
|
||||
|
||||
p.color = Color.BLACK
|
||||
p.textSize = 28f
|
||||
p.typeface = Typeface.DEFAULT_BOLD
|
||||
p.textAlign = Paint.Align.LEFT
|
||||
p.style = Paint.Style.FILL
|
||||
val maxW = rect.width() - 20f
|
||||
val nameChars = p.breakText(name, true, maxW - p.measureText("…"), null)
|
||||
val nameStr = if (nameChars < name.length) name.take(nameChars) + "…" else name
|
||||
canvas.drawText(nameStr, textLeft, rect.top + 36f, p)
|
||||
|
||||
p.color = Color.DKGRAY
|
||||
p.textSize = 22f
|
||||
p.typeface = Typeface.DEFAULT
|
||||
canvas.drawText(detail, textLeft, rect.top + 66f, p)
|
||||
}
|
||||
|
||||
/** Green-tinted block showing battery bank totals (capacity, energy, usable). */
|
||||
private fun drawBatteryBlock(context: Context, canvas: Canvas, state: DetailState, rect: RectF) {
|
||||
val p = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
val r = Color.red(FB_BATTERY_COLOR); val g = Color.green(FB_BATTERY_COLOR); val b = Color.blue(FB_BATTERY_COLOR)
|
||||
|
||||
p.color = Color.argb(20, r, g, b)
|
||||
p.style = Paint.Style.FILL
|
||||
canvas.drawRoundRect(rect, 14f, 14f, p)
|
||||
|
||||
p.color = Color.argb(100, r, g, b)
|
||||
p.style = Paint.Style.STROKE
|
||||
p.strokeWidth = 2.5f
|
||||
canvas.drawRoundRect(rect, 14f, 14f, p)
|
||||
|
||||
p.style = Paint.Style.FILL
|
||||
p.textAlign = Paint.Align.CENTER
|
||||
val cx = rect.centerX()
|
||||
|
||||
if (state.batteries.isEmpty()) {
|
||||
p.color = Color.GRAY
|
||||
p.textSize = 24f
|
||||
p.typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
|
||||
canvas.drawText(context.getString(R.string.diagram_battery_no_batteries), cx, rect.centerY() + 10f, p)
|
||||
return
|
||||
}
|
||||
|
||||
val m = state.metrics
|
||||
|
||||
p.color = FB_BATTERY_COLOR
|
||||
p.textSize = 48f
|
||||
p.typeface = Typeface.DEFAULT
|
||||
canvas.drawText("⚡", cx, rect.top + 60f, p)
|
||||
|
||||
p.color = Color.BLACK
|
||||
p.textSize = 42f
|
||||
p.typeface = Typeface.DEFAULT_BOLD
|
||||
canvas.drawText("${Fmt.number(m.totalCapacity)} Ah", cx, rect.top + 112f, p)
|
||||
|
||||
p.color = Color.DKGRAY
|
||||
p.textSize = 32f
|
||||
p.typeface = Typeface.DEFAULT
|
||||
canvas.drawText("${Fmt.number(m.totalEnergy)} Wh", cx, rect.top + 152f, p)
|
||||
|
||||
p.color = Color.GRAY
|
||||
p.textSize = 26f
|
||||
canvas.drawText("${context.getString(R.string.diagram_battery_usable)}: ${Fmt.number(m.totalUsableCapacity)} Ah", cx, rect.top + 186f, p)
|
||||
|
||||
if (state.batteries.size > 1) {
|
||||
p.color = FB_BATTERY_COLOR
|
||||
p.textSize = 26f
|
||||
canvas.drawText("${state.batteries.size}×", cx, rect.bottom - 22f, p)
|
||||
}
|
||||
}
|
||||
|
||||
/** Cubic bezier arrow from (x1, y1) to (x2, y2) with a filled triangular head. */
|
||||
private fun drawArrow(canvas: Canvas, x1: Float, y1: Float, x2: Float, y2: Float, color: Int) {
|
||||
val cr = Color.red(color); val cg = Color.green(color); val cb = Color.blue(color)
|
||||
val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
this.color = Color.argb(128, cr, cg, cb)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 2.5f
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
}
|
||||
|
||||
val ctrlX = (x1 + x2) / 2f
|
||||
val curvePath = Path().apply {
|
||||
moveTo(x1, y1)
|
||||
cubicTo(ctrlX, y1, ctrlX, y2, x2, y2)
|
||||
}
|
||||
canvas.drawPath(curvePath, linePaint)
|
||||
|
||||
val angle = atan2((y2 - y1).toDouble(), (x2 - x1).toDouble())
|
||||
val s = 10.0
|
||||
val headPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
this.color = Color.argb(128, cr, cg, cb)
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
val headPath = Path().apply {
|
||||
moveTo(x2, y2)
|
||||
lineTo((x2 - s * cos(angle - Math.PI / 6)).toFloat(), (y2 - s * sin(angle - Math.PI / 6)).toFloat())
|
||||
lineTo((x2 - s * cos(angle + Math.PI / 6)).toFloat(), (y2 - s * sin(angle + Math.PI / 6)).toFloat())
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(headPath, headPaint)
|
||||
}
|
||||
|
||||
private fun drawDash(canvas: Canvas, cx: Float, y: Float) {
|
||||
val p = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.GRAY; textSize = 28f; textAlign = Paint.Align.CENTER
|
||||
}
|
||||
canvas.drawText("–", cx, y, p)
|
||||
}
|
||||
|
||||
private fun drawMore(canvas: Canvas, text: String, cx: Float, y: Float) {
|
||||
val p = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.GRAY; textSize = 26f; textAlign = Paint.Align.CENTER
|
||||
}
|
||||
canvas.drawText(text, cx, y, p)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private fun flattenOnWhite(source: Bitmap): Bitmap {
|
||||
val result = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
|
||||
Canvas(result).apply {
|
||||
|
||||
@@ -28,8 +28,8 @@ private val ACCENT = Color.rgb(115, 87, 219)
|
||||
/** Renders a full system overview PDF and opens the Android share sheet. */
|
||||
object SystemOverviewPdf {
|
||||
suspend fun exportAndShare(context: Context, state: DetailState, unit: UnitSystem) {
|
||||
// Fetch the wiring diagram first (falls back to no diagram page if unavailable).
|
||||
val diagram = SystemDiagram.fetch(state, unit)
|
||||
// Fetch the wiring diagram; falls back to a local block diagram if the API is unreachable.
|
||||
val diagram = SystemDiagram.fetchOrFallback(context, state, unit)
|
||||
val file = withContext(Dispatchers.IO) {
|
||||
val doc = PdfDocument()
|
||||
val w = PdfWriter(doc)
|
||||
@@ -49,8 +49,8 @@ object SystemOverviewPdf {
|
||||
summaryLine(w, context.getString(R.string.overview_pdf_summary_batterycapacity), "${Fmt.number(m.totalUsableEnergy)} Wh")
|
||||
summaryLine(w, context.getString(R.string.overview_pdf_summary_chargerpower), "${Fmt.number(m.totalChargerPower)} W")
|
||||
|
||||
// Full-page wiring diagram, followed by a fresh page for the entity tables.
|
||||
diagram?.let { drawDiagramPage(w, it); w.beginPage() }
|
||||
// Full-page wiring diagram (always present — API result or local fallback).
|
||||
drawDiagramPage(w, diagram); w.beginPage()
|
||||
|
||||
if (state.loads.isNotEmpty()) {
|
||||
w.gap(12f); w.text(context.getString(R.string.overview_pdf_loads_section), 18f, ACCENT, bold = true); w.divider()
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.ChevronRight
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -28,6 +29,7 @@ import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -59,6 +61,28 @@ fun SystemsScreen(
|
||||
vm: SystemsViewModel = viewModel(),
|
||||
) {
|
||||
val summaries by vm.systems.collectAsStateWithLifecycle()
|
||||
var systemPendingDeletion by remember { mutableStateOf<SystemSummary?>(null) }
|
||||
|
||||
systemPendingDeletion?.let { pending ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { systemPendingDeletion = null },
|
||||
title = { Text(stringResource(R.string.delete_system_confirm_title)) },
|
||||
text = { Text(stringResource(R.string.delete_system_confirm_message)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.deleteSystem(pending)
|
||||
systemPendingDeletion = null
|
||||
}) {
|
||||
Text(stringResource(R.string.action_delete))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { systemPendingDeletion = null }) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -99,7 +123,7 @@ fun SystemsScreen(
|
||||
vm.logOpen(summary, "list")
|
||||
onOpenSystem(summary.system.id)
|
||||
},
|
||||
onDelete = { vm.deleteSystem(summary) },
|
||||
onDelete = { systemPendingDeletion = summary },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
<!-- Batteries -->
|
||||
<string name="battery_bank_header_title">Batterien</string>
|
||||
<string name="diagram_battery_no_batteries">Keine Batterien</string>
|
||||
<string name="diagram_battery_usable">Nutzbar</string>
|
||||
<string name="battery_metric_count">Batterien</string>
|
||||
<string name="battery_metric_capacity">Kapazität</string>
|
||||
<string name="battery_metric_usable_capacity">Nutzbare Kapazität</string>
|
||||
@@ -275,4 +277,6 @@
|
||||
<string name="default_battery_new">Neue Batterie</string>
|
||||
<string name="default_charger_new">Neues Ladegerät</string>
|
||||
<string name="default_system_new">Neues System</string>
|
||||
<string name="delete_system_confirm_title">System löschen?</string>
|
||||
<string name="delete_system_confirm_message">Das System und alle seine Komponenten werden dauerhaft gelöscht.</string>
|
||||
</resources>
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
<!-- Batteries -->
|
||||
<string name="battery_bank_header_title">Banco de baterías</string>
|
||||
<string name="diagram_battery_no_batteries">Sin baterías</string>
|
||||
<string name="diagram_battery_usable">Utilizable</string>
|
||||
<string name="battery_metric_count">Baterías</string>
|
||||
<string name="battery_metric_capacity">Capacidad</string>
|
||||
<string name="battery_metric_usable_capacity">Capacidad utilizable</string>
|
||||
@@ -275,4 +277,6 @@
|
||||
<string name="default_battery_new">Nueva batería</string>
|
||||
<string name="default_charger_new">Nuevo cargador</string>
|
||||
<string name="default_system_new">Sistema nuevo</string>
|
||||
<string name="delete_system_confirm_title">¿Eliminar sistema?</string>
|
||||
<string name="delete_system_confirm_message">Esto eliminará permanentemente el sistema y todos sus componentes.</string>
|
||||
</resources>
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
<!-- Batteries -->
|
||||
<string name="battery_bank_header_title">Banque de batteries</string>
|
||||
<string name="diagram_battery_no_batteries">Pas de batteries</string>
|
||||
<string name="diagram_battery_usable">Utilisable</string>
|
||||
<string name="battery_metric_count">Batteries</string>
|
||||
<string name="battery_metric_capacity">Capacité</string>
|
||||
<string name="battery_metric_usable_capacity">Capacité utilisable</string>
|
||||
@@ -275,4 +277,6 @@
|
||||
<string name="default_battery_new">Nouvelle batterie</string>
|
||||
<string name="default_charger_new">Nouveau chargeur</string>
|
||||
<string name="default_system_new">Nouveau système</string>
|
||||
<string name="delete_system_confirm_title">Supprimer le système ?</string>
|
||||
<string name="delete_system_confirm_message">Cela supprimera définitivement le système et tous ses composants.</string>
|
||||
</resources>
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
<!-- Batteries -->
|
||||
<string name="battery_bank_header_title">Accubank</string>
|
||||
<string name="diagram_battery_no_batteries">Geen batterijen</string>
|
||||
<string name="diagram_battery_usable">Bruikbaar</string>
|
||||
<string name="battery_metric_count">Batterijen</string>
|
||||
<string name="battery_metric_capacity">Capaciteit</string>
|
||||
<string name="battery_metric_usable_capacity">Beschikbare capaciteit</string>
|
||||
@@ -275,4 +277,6 @@
|
||||
<string name="default_battery_new">Nieuwe batterij</string>
|
||||
<string name="default_charger_new">Nieuwe lader</string>
|
||||
<string name="default_system_new">Nieuw systeem</string>
|
||||
<string name="delete_system_confirm_title">Systeem verwijderen?</string>
|
||||
<string name="delete_system_confirm_message">Dit verwijdert het systeem en alle bijbehorende componenten permanent.</string>
|
||||
</resources>
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
|
||||
<!-- Batteries -->
|
||||
<string name="battery_bank_header_title">Battery Bank</string>
|
||||
<string name="diagram_battery_no_batteries">No batteries</string>
|
||||
<string name="diagram_battery_usable">Usable</string>
|
||||
<string name="battery_metric_count">Batteries</string>
|
||||
<string name="battery_metric_capacity">Capacity</string>
|
||||
<string name="battery_metric_usable_capacity">Usable Capacity</string>
|
||||
@@ -275,4 +277,6 @@
|
||||
<string name="default_battery_new">New Battery</string>
|
||||
<string name="default_charger_new">New Charger</string>
|
||||
<string name="default_system_new">New System</string>
|
||||
<string name="delete_system_confirm_title">Delete System?</string>
|
||||
<string name="delete_system_confirm_message">This will permanently delete the system and all its components.</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user