From d97e3a2b7cc29e79590554e6f3106328b258e363 Mon Sep 17 00:00:00 2001 From: Stefan Lange-Hegermann Date: Thu, 4 Jun 2026 01:04:11 +0200 Subject: [PATCH] Add standalone wiring diagram PNG export Fetch the wiring diagram from the VoltPlan API and share it as a standalone PNG from the Overview share menu on both platforms, with a localized error when the diagram cannot be generated. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cable/Loads/LoadsView.swift | 140 +++++++++++++----- .../java/app/voltplan/cable/pdf/PdfShare.kt | 6 +- .../app/voltplan/cable/pdf/SystemDiagram.kt | 133 +++++++++++++++++ .../voltplan/cable/pdf/SystemOverviewPdf.kt | 28 ++++ .../cable/ui/system/SystemDetailScreen.kt | 86 ++++++++--- .../app/src/main/res/values-de/strings.xml | 3 + .../app/src/main/res/values-es/strings.xml | 3 + .../app/src/main/res/values-fr/strings.xml | 3 + .../app/src/main/res/values-nl/strings.xml | 3 + android/app/src/main/res/values/strings.xml | 3 + 10 files changed, 350 insertions(+), 58 deletions(-) create mode 100644 android/app/src/main/java/app/voltplan/cable/pdf/SystemDiagram.kt diff --git a/Cable/Loads/LoadsView.swift b/Cable/Loads/LoadsView.swift index 0581f77..354cf26 100644 --- a/Cable/Loads/LoadsView.swift +++ b/Cable/Loads/LoadsView.swift @@ -19,7 +19,7 @@ struct LoadsView: View { @State private var showingSystemEditor = false @State private var hasPresentedSystemEditorOnAppear = false @State private var hasOpenedLoadOnAppear = false - @State private var showingComponentLibrary = false + @State private var activeLibrary: ComponentLibraryType? @State private var showingSystemBOM = false @State private var selectedComponentTab: ComponentTab @State private var batteryDraft: BatteryConfiguration? @@ -86,23 +86,7 @@ struct LoadsView: View { .accessibilityIdentifier("components-tab") } - Group { - if savedBatteries.isEmpty { - OnboardingInfoView( - configuration: .battery(), - onPrimaryAction: { startBatteryConfiguration() } - ) - } else { - BatteriesView( - system: system, - batteries: savedBatteries, - editMode: $editMode, - onEdit: { editBattery($0) }, - onDelete: deleteBatteries - ) - .environment(\.editMode, $editMode) - } - } + batteriesTab .tag(ComponentTab.batteries) .tabItem { Label( @@ -117,14 +101,7 @@ struct LoadsView: View { } .environment(\.editMode, $editMode) - ChargersView( - system: system, - chargers: savedChargers, - editMode: $editMode, - onAdd: { startChargerConfiguration() }, - onEdit: { editCharger($0) }, - onDelete: deleteChargers - ) + chargersTab .tag(ComponentTab.chargers) .tabItem { Label( @@ -272,9 +249,9 @@ struct LoadsView: View { exportDiagramImage() } } - .sheet(isPresented: $showingComponentLibrary) { - ComponentLibraryView { item in - addComponent(item) + .sheet(item: $activeLibrary) { type in + ComponentLibraryView(libraryType: type) { item in + handleLibrarySelection(item, for: type) } } .sheet(isPresented: $showingSystemBOM) { @@ -439,9 +416,9 @@ struct LoadsView: View { } } - private var libraryButton: some View { + private func libraryButton(type: ComponentLibraryType) -> some View { Button { - openComponentLibrary(source: "library-button") + openComponentLibrary(source: "library-button", type: type) } label: { Group { if #available(iOS 26.0, *) { @@ -490,6 +467,53 @@ struct LoadsView: View { .background(Color(.systemGroupedBackground)) } + private var batteriesTab: some View { + Group { + if savedBatteries.isEmpty { + OnboardingInfoView( + configuration: .battery(), + onPrimaryAction: { startBatteryConfiguration() }, + onSecondaryAction: { openComponentLibrary(source: "batteries-onboarding", type: .battery) } + ) + } else { + BatteriesView( + system: system, + batteries: savedBatteries, + editMode: $editMode, + onEdit: { editBattery($0) }, + onDelete: deleteBatteries + ) + .environment(\.editMode, $editMode) + } + } + .overlay(alignment: .bottomTrailing) { + if !savedBatteries.isEmpty { + libraryButton(type: .battery) + .padding(.trailing, 24) + .padding(.bottom, 24) + } + } + } + + private var chargersTab: some View { + ChargersView( + system: system, + chargers: savedChargers, + editMode: $editMode, + onAdd: { startChargerConfiguration() }, + onEdit: { editCharger($0) }, + onDelete: deleteChargers, + onBrowseLibrary: { openComponentLibrary(source: "chargers-onboarding", type: .charger) } + ) + .overlay(alignment: .bottomTrailing) { + if !savedChargers.isEmpty { + libraryButton(type: .charger) + .padding(.trailing, 24) + .padding(.bottom, 24) + } + } + } + @ViewBuilder private var loadsListWithHeader: some View { Group { @@ -507,7 +531,7 @@ struct LoadsView: View { } } .overlay(alignment: .bottomTrailing) { - libraryButton + libraryButton(type: .load) .padding(.trailing, 24) .padding(.bottom, 24) } @@ -802,15 +826,63 @@ struct LoadsView: View { showingSystemEditor = true } - private func openComponentLibrary(source: String) { + private func openComponentLibrary(source: String, type: ComponentLibraryType = .load) { AnalyticsTracker.log( "Component Library Opened", properties: [ "source": source, + "type": type.rawValue, "system": system.name ] ) - showingComponentLibrary = true + activeLibrary = type + } + + private func handleLibrarySelection(_ item: ComponentLibraryItem, for type: ComponentLibraryType) { + switch type { + case .load: + addComponent(item) + case .battery: + addBatteryFromLibrary(item) + case .charger: + addChargerFromLibrary(item) + } + } + + private func addBatteryFromLibrary(_ item: ComponentLibraryItem) { + AnalyticsTracker.log( + "Library Battery Added", + properties: [ + "id": item.id, + "name": item.localizedName, + "system": system.name + ] + ) + batteryDraft = SystemComponentsPersistence.makeBatteryDraft( + from: item, + for: system, + existingLoads: savedLoads, + existingBatteries: savedBatteries, + existingChargers: savedChargers + ) + } + + private func addChargerFromLibrary(_ item: ComponentLibraryItem) { + AnalyticsTracker.log( + "Library Charger Added", + properties: [ + "id": item.id, + "name": item.localizedName, + "system": system.name + ] + ) + chargerDraft = SystemComponentsPersistence.makeChargerDraft( + from: item, + for: system, + existingLoads: savedLoads, + existingBatteries: savedBatteries, + existingChargers: savedChargers + ) } private func openBillOfMaterials() { diff --git a/android/app/src/main/java/app/voltplan/cable/pdf/PdfShare.kt b/android/app/src/main/java/app/voltplan/cable/pdf/PdfShare.kt index 619e11c..cf19d40 100644 --- a/android/app/src/main/java/app/voltplan/cable/pdf/PdfShare.kt +++ b/android/app/src/main/java/app/voltplan/cable/pdf/PdfShare.kt @@ -75,10 +75,12 @@ object PdfShare { return file } - fun share(context: Context, file: File) { + fun share(context: Context, file: File) = shareFile(context, file, "application/pdf") + + fun shareFile(context: Context, file: File, mimeType: String) { val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) val intent = Intent(Intent.ACTION_SEND).apply { - type = "application/pdf" + type = mimeType putExtra(Intent.EXTRA_STREAM, uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } diff --git a/android/app/src/main/java/app/voltplan/cable/pdf/SystemDiagram.kt b/android/app/src/main/java/app/voltplan/cable/pdf/SystemDiagram.kt new file mode 100644 index 0000000..3b90b28 --- /dev/null +++ b/android/app/src/main/java/app/voltplan/cable/pdf/SystemDiagram.kt @@ -0,0 +1,133 @@ +package app.voltplan.cable.pdf + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Color +import app.voltplan.cable.R +import app.voltplan.cable.analytics.Analytics +import app.voltplan.cable.data.model.effectivePowerWatts +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File +import java.util.concurrent.TimeUnit + +/** + * 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() + private val client = OkHttpClient.Builder() + .callTimeout(15, TimeUnit.SECONDS) + .build() + + /** Fetches the diagram as a [Bitmap], or null on any network/decoding failure. */ + suspend fun fetch(state: DetailState, unit: UnitSystem): Bitmap? = withContext(Dispatchers.IO) { + val payload = buildPayload(state, unit) + val request = Request.Builder() + .url(ENDPOINT) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "image/png") + .post(Json.encodeToString(JsonObject.serializer(), payload).toRequestBody(JSON_MEDIA)) + .build() + + runCatching { + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) return@use null + response.body?.bytes()?.let { BitmapFactory.decodeByteArray(it, 0, it.size) } + } + }.getOrNull() + } + + /** Fetches the diagram, flattens it onto a white background, and opens the Android share sheet. */ + 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 file = withContext(Dispatchers.IO) { + val opaque = flattenOnWhite(bitmap) + val name = state.system?.name?.takeIf { it.isNotBlank() } ?: "System" + val dir = File(context.cacheDir, "exports").apply { mkdirs() } + val out = File(dir, "${name.replace(Regex("[^A-Za-z0-9-_]"), "_")}-Diagram.png") + out.outputStream().use { opaque.compress(Bitmap.CompressFormat.PNG, 100, it) } + out + } + withContext(Dispatchers.Main) { + Analytics.log("Diagram Image Shared", mapOf("system" to (state.system?.name ?: ""))) + PdfShare.shareFile(context, file, "image/png") + } + } + + private fun flattenOnWhite(source: Bitmap): Bitmap { + val result = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888) + Canvas(result).apply { + drawColor(Color.WHITE) + drawBitmap(source, 0f, 0f, null) + } + return result + } + + private fun buildPayload(state: DetailState, unit: UnitSystem): JsonObject = buildJsonObject { + put("systemName", state.system?.name ?: "System") + put("source", "cable") + put("unitSystem", if (unit == UnitSystem.METRIC) "metric" else "imperial") + put("loads", buildJsonArray { + state.loads.forEach { load -> + add(buildJsonObject { + put("name", load.name) + put("power", load.power) + put("voltage", load.voltage) + put("current", load.current) + load.remoteIconURLString?.let { put("iconUrl", it) } + }) + } + }) + put("batteries", buildJsonArray { + state.batteries.forEach { battery -> + add(buildJsonObject { + put("name", battery.name) + put("voltage", battery.nominalVoltage) + put("capacityAh", battery.capacityAmpHours) + put("energyWh", battery.energyWattHours) + }) + } + }) + put("chargers", buildJsonArray { + state.chargers.forEach { charger -> + add(buildJsonObject { + put("name", charger.name) + put("inputVoltage", charger.inputVoltage) + put("outputVoltage", charger.outputVoltage) + put("power", charger.effectivePowerWatts) + put("sourceType", charger.sourceType.rawValue) + charger.remoteIconURLString?.let { put("iconUrl", it) } + }) + } + }) + } +} diff --git a/android/app/src/main/java/app/voltplan/cable/pdf/SystemOverviewPdf.kt b/android/app/src/main/java/app/voltplan/cable/pdf/SystemOverviewPdf.kt index ffd6aa5..d2d0a5e 100644 --- a/android/app/src/main/java/app/voltplan/cable/pdf/SystemOverviewPdf.kt +++ b/android/app/src/main/java/app/voltplan/cable/pdf/SystemOverviewPdf.kt @@ -1,7 +1,11 @@ package app.voltplan.cable.pdf import android.content.Context +import android.graphics.Bitmap import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.RectF import android.graphics.pdf.PdfDocument import app.voltplan.cable.R import app.voltplan.cable.calc.ElectricalCalculations @@ -23,6 +27,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) val file = withContext(Dispatchers.IO) { val doc = PdfDocument() val w = PdfWriter(doc) @@ -42,6 +48,9 @@ object SystemOverviewPdf { summaryLine(w, context.getString(R.string.overview_pdf_summary_batterycapacity), "${Fmt.number(m.totalCapacity)} Ah") 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() } + if (state.loads.isNotEmpty()) { w.gap(12f); w.text(context.getString(R.string.overview_pdf_loads_section), 18f, ACCENT, bold = true); w.divider() state.loads.forEach { load -> @@ -89,4 +98,23 @@ object SystemOverviewPdf { private fun summaryLine(w: PdfWriter, label: String, value: String) { w.text("$label: $value", 12f, Color.DKGRAY) } + + /** Draws the diagram bitmap on its own page, scaled to fit the margins while keeping aspect ratio. */ + private fun drawDiagramPage(w: PdfWriter, diagram: Bitmap) { + w.beginPage() + val availableWidth = PAGE_W - MARGIN * 2 + val availableHeight = PAGE_H - MARGIN * 2 - 30 // leave room for footer + val imageAspect = diagram.width.toFloat() / diagram.height.toFloat() + val rectAspect = availableWidth / availableHeight + + val dest = if (imageAspect > rectAspect) { + val drawHeight = availableWidth / imageAspect + RectF(MARGIN, MARGIN + (availableHeight - drawHeight) / 2f, MARGIN + availableWidth, MARGIN + (availableHeight - drawHeight) / 2f + drawHeight) + } else { + val drawWidth = availableHeight * imageAspect + RectF(MARGIN + (availableWidth - drawWidth) / 2f, MARGIN, MARGIN + (availableWidth - drawWidth) / 2f + drawWidth, MARGIN + availableHeight) + } + val src = Rect(0, 0, diagram.width, diagram.height) + w.canvas.drawBitmap(diagram, src, dest, Paint().apply { isFilterBitmap = true }) + } } diff --git a/android/app/src/main/java/app/voltplan/cable/ui/system/SystemDetailScreen.kt b/android/app/src/main/java/app/voltplan/cable/ui/system/SystemDetailScreen.kt index 4b3f0b3..c0ea4c0 100644 --- a/android/app/src/main/java/app/voltplan/cable/ui/system/SystemDetailScreen.kt +++ b/android/app/src/main/java/app/voltplan/cable/ui/system/SystemDetailScreen.kt @@ -13,11 +13,13 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.filled.BatteryFull +import androidx.compose.material.icons.filled.Bolt as BoltFilled +import androidx.compose.material.icons.filled.Dashboard +import androidx.compose.material.icons.filled.Layers import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.BatteryFull import androidx.compose.material.icons.outlined.Bolt -import androidx.compose.material.icons.outlined.Dashboard -import androidx.compose.material.icons.outlined.Layers +import androidx.compose.material.icons.outlined.IosShare import androidx.compose.material.icons.outlined.PictureAsPdf import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -39,6 +41,7 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import app.voltplan.cable.CableApplication import app.voltplan.cable.R +import app.voltplan.cable.library.ComponentLibraryType import app.voltplan.cable.ui.LocalUnitSettings import app.voltplan.cable.ui.batteries.BatteriesTab import app.voltplan.cable.ui.chargers.ChargersTab @@ -48,7 +51,10 @@ import app.voltplan.cable.ui.overview.OverviewTab import app.voltplan.cable.ui.sfSymbol import app.voltplan.cable.ui.systemIconOptions import app.voltplan.cable.ui.theme.componentColor +import app.voltplan.cable.pdf.SystemDiagram import app.voltplan.cable.pdf.SystemOverviewPdf +import android.widget.Toast +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -82,7 +88,7 @@ fun SystemDetailScreen( onEditCharger: (String) -> Unit, onNewCharger: () -> Unit, onOpenBom: () -> Unit, - onOpenLibrary: () -> Unit, + onOpenLibrary: (ComponentLibraryType) -> Unit, ) { val context = LocalContext.current val app = context.applicationContext as CableApplication @@ -98,8 +104,15 @@ fun SystemDetailScreen( var tab by rememberSaveableTab() var showSystemEditor by remember { mutableStateOf(false) } var showOverviewMenu by remember { mutableStateOf(false) } + var exporting by remember { mutableStateOf(false) } val system = state.system + // Switch to the matching tab before opening an editor, so returning from the + // editor lands on that tab with the newly created component visible. + val newLoad = { tab = ComponentTab.COMPONENTS; onNewLoad() } + val newBattery = { tab = ComponentTab.BATTERIES; onNewBattery() } + val newCharger = { tab = ComponentTab.CHARGERS; onNewCharger() } + Scaffold( topBar = { TopAppBar( @@ -132,28 +145,52 @@ fun SystemDetailScreen( actions = { when (tab) { ComponentTab.OVERVIEW -> { - IconButton(onClick = { showOverviewMenu = true }) { - Icon(Icons.Outlined.PictureAsPdf, contentDescription = stringResource(R.string.overview_share_pdf)) + if (exporting) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp).padding(end = 12.dp), + strokeWidth = 2.dp, + ) + } else { + IconButton(onClick = { showOverviewMenu = true }) { + Icon(Icons.Outlined.IosShare, contentDescription = stringResource(R.string.overview_share_pdf)) + } } DropdownMenu(expanded = showOverviewMenu, onDismissRequest = { showOverviewMenu = false }) { DropdownMenuItem( + leadingIcon = { Icon(Icons.Outlined.Bolt, contentDescription = null) }, + text = { Text(stringResource(R.string.overview_share_diagram)) }, + onClick = { + showOverviewMenu = false + scope.launch { + exporting = true + SystemDiagram.exportAndShare(context, state, unitSystem) { + Toast.makeText(context, R.string.overview_share_diagram_error, Toast.LENGTH_LONG).show() + } + exporting = false + } + }, + ) + DropdownMenuItem( + leadingIcon = { Icon(Icons.Outlined.PictureAsPdf, contentDescription = null) }, text = { Text(stringResource(R.string.overview_share_pdf)) }, onClick = { showOverviewMenu = false scope.launch { + exporting = true SystemOverviewPdf.exportAndShare(context, state, unitSystem) + exporting = false } }, ) } } - ComponentTab.COMPONENTS -> IconButton(onClick = onNewLoad) { + ComponentTab.COMPONENTS -> IconButton(onClick = newLoad) { Icon(Icons.Outlined.Add, contentDescription = stringResource(R.string.action_add)) } - ComponentTab.BATTERIES -> IconButton(onClick = onNewBattery) { + ComponentTab.BATTERIES -> IconButton(onClick = newBattery) { Icon(Icons.Outlined.Add, contentDescription = stringResource(R.string.action_add)) } - ComponentTab.CHARGERS -> IconButton(onClick = onNewCharger) { + ComponentTab.CHARGERS -> IconButton(onClick = newCharger) { Icon(Icons.Outlined.Add, contentDescription = stringResource(R.string.action_add)) } } @@ -162,10 +199,10 @@ fun SystemDetailScreen( }, bottomBar = { NavigationBar { - NavTab(tab, ComponentTab.OVERVIEW, Icons.Outlined.Dashboard, stringResource(R.string.tab_overview)) { tab = it; vm.logTabChange(it.analytics) } - NavTab(tab, ComponentTab.COMPONENTS, Icons.Outlined.Layers, stringResource(R.string.tab_components)) { tab = it; vm.logTabChange(it.analytics) } - NavTab(tab, ComponentTab.BATTERIES, Icons.Outlined.BatteryFull, stringResource(R.string.tab_batteries)) { tab = it; vm.logTabChange(it.analytics) } - NavTab(tab, ComponentTab.CHARGERS, Icons.Outlined.Bolt, stringResource(R.string.tab_chargers)) { tab = it; vm.logTabChange(it.analytics) } + NavTab(tab, ComponentTab.OVERVIEW, Icons.Filled.Dashboard, stringResource(R.string.tab_overview)) { tab = it; vm.logTabChange(it.analytics) } + NavTab(tab, ComponentTab.COMPONENTS, Icons.Filled.Layers, stringResource(R.string.tab_components)) { tab = it; vm.logTabChange(it.analytics) } + NavTab(tab, ComponentTab.BATTERIES, Icons.Filled.BatteryFull, stringResource(R.string.tab_batteries)) { tab = it; vm.logTabChange(it.analytics) } + NavTab(tab, ComponentTab.CHARGERS, Icons.Filled.BoltFilled, stringResource(R.string.tab_chargers)) { tab = it; vm.logTabChange(it.analytics) } } }, ) { padding -> @@ -174,11 +211,14 @@ fun SystemDetailScreen( ComponentTab.OVERVIEW -> OverviewTab( state = state, unitSystem = unitSystem, - onAddLoad = onNewLoad, - onAddBattery = onNewBattery, - onAddCharger = onNewCharger, - onOpenLibrary = onOpenLibrary, + onAddLoad = newLoad, + onAddBattery = newBattery, + onAddCharger = newCharger, + onOpenLibrary = { onOpenLibrary(ComponentLibraryType.LOAD) }, onOpenBom = { vm.logBomOpened(); onOpenBom() }, + onSelectLoads = { tab = ComponentTab.COMPONENTS; vm.logTabChange(ComponentTab.COMPONENTS.analytics) }, + onSelectBatteries = { tab = ComponentTab.BATTERIES; vm.logTabChange(ComponentTab.BATTERIES.analytics) }, + onSelectChargers = { tab = ComponentTab.CHARGERS; vm.logTabChange(ComponentTab.CHARGERS.analytics) }, onSetRuntimeGoal = vm::setRuntimeGoal, onSetChargeGoal = vm::setChargeGoal, ) @@ -186,20 +226,22 @@ fun SystemDetailScreen( state = state, unitSystem = unitSystem, onOpenLoad = onOpenLoad, - onNewLoad = onNewLoad, - onOpenLibrary = onOpenLibrary, + onNewLoad = newLoad, + onOpenLibrary = { onOpenLibrary(ComponentLibraryType.LOAD) }, onDeleteLoad = vm::deleteLoad, ) ComponentTab.BATTERIES -> BatteriesTab( state = state, onEditBattery = onEditBattery, - onNewBattery = onNewBattery, + onNewBattery = newBattery, + onOpenLibrary = { onOpenLibrary(ComponentLibraryType.BATTERY) }, onDeleteBattery = vm::deleteBattery, ) ComponentTab.CHARGERS -> ChargersTab( state = state, onEditCharger = onEditCharger, - onNewCharger = onNewCharger, + onNewCharger = newCharger, + onOpenLibrary = { onOpenLibrary(ComponentLibraryType.CHARGER) }, onDeleteCharger = vm::deleteCharger, ) } @@ -248,4 +290,4 @@ private fun RowScope.NavTab( } @Composable -private fun rememberSaveableTab() = remember { mutableStateOf(ComponentTab.OVERVIEW) } +private fun rememberSaveableTab() = rememberSaveable { mutableStateOf(ComponentTab.OVERVIEW) } diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index 7eb699f..8f0f991 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -5,6 +5,7 @@ Hinzufügen Zurück + Speichern Löschen @@ -177,6 +178,8 @@ Füge Landstrom-, DC-DC- oder Solarladegeräte hinzu, um deine Ladeleistung zu verstehen. Ladegerät hinzufügen Vollständiger Bericht (PDF) + Schaltplan + Schaltplan konnte nicht erstellt werden. Überprüfe deine Internetverbindung. Tage diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml index 9d0279f..9b7e249 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -5,6 +5,7 @@ Añadir Atrás + Guardar Eliminar @@ -177,6 +178,8 @@ Añade cargadores de toma de puerto, DC-DC o solares para conocer tu capacidad de carga. Añadir cargador Informe completo (PDF) + Diagrama de cableado + No se pudo generar el diagrama. Comprueba tu conexión a Internet. Días diff --git a/android/app/src/main/res/values-fr/strings.xml b/android/app/src/main/res/values-fr/strings.xml index 5c6b61d..c3fef4d 100644 --- a/android/app/src/main/res/values-fr/strings.xml +++ b/android/app/src/main/res/values-fr/strings.xml @@ -5,6 +5,7 @@ Ajouter Retour + Enregistrer Supprimer @@ -177,6 +178,8 @@ Ajoutez des chargeurs secteur, DC-DC ou solaires pour comprendre votre capacité de charge. Ajouter un chargeur Rapport complet (PDF) + Schéma de câblage + Impossible de générer le schéma. Vérifiez votre connexion Internet. Jours diff --git a/android/app/src/main/res/values-nl/strings.xml b/android/app/src/main/res/values-nl/strings.xml index 1e18d86..6d25dd0 100644 --- a/android/app/src/main/res/values-nl/strings.xml +++ b/android/app/src/main/res/values-nl/strings.xml @@ -5,6 +5,7 @@ Toevoegen Terug + Opslaan Verwijderen @@ -177,6 +178,8 @@ Voeg walstroom-, DC-DC- of zonneladers toe om je laadcapaciteit te begrijpen. Lader toevoegen Volledig rapport (PDF) + Bedradingsschema + Diagram kon niet worden gegenereerd. Controleer je internetverbinding. Dagen diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 798cb44..754518d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Add Back + Save Delete @@ -177,6 +178,8 @@ Add shore power, DC-DC, or solar chargers to understand your charging capacity. Add Charger Full Report (PDF) + Wiring Diagram + Could not generate diagram. Check your internet connection. Days