feat(update): AlertDialog bei verfuegbarem Update anzeigen

- AlertDialog in MainScreen zeigt verfuegbare Version mit Bestaetigung
- UpdateBanner blendet bei UpdateStatus.Available aus (Dialog uebernimmt)
- FEATURE_CAMERA_ENABLED temporaer deaktiviert

fix(server): Logo-Pfad und statische Ressourcen bereinigen

- /res-Route fuer classpath-Assets (logo.png etc.) hinzugefuegt
- Logo-Pfad von /static/logo.png auf /res/logo.png korrigiert
- Build-Nummer aus Versionsanzeige auf der Homepage entfernt

ci: GitHub Actions Workflow fuer Swift-Tests hinzugefuegt

style: Tabellenformatierung im Code-Reviewer-Agenten bereinigt
This commit is contained in:
Jens Reinemann 2026-05-17 20:52:47 +02:00
parent 9ff21cbc4b
commit 3d7c01cef5
8 changed files with 121 additions and 11 deletions

View file

@ -150,11 +150,11 @@ Korrektur: <wie es sein sollte>
#### Schweregrade #### Schweregrade
| Schwere | Bedeutung | | Schwere | Bedeutung |
|---|---| | ------------ | ------------------------------------------------------------------------------- |
| **KRITISCH** | Falsches Verhalten, Crash-Potenzial, Sicherheitslücke muss behoben werden | | **KRITISCH** | Falsches Verhalten, Crash-Potenzial, Sicherheitslücke muss behoben werden |
| **WICHTIG** | Standard-Verletzung, Codedopplung, schlechte Lesbarkeit sollte behoben werden | | **WICHTIG** | Standard-Verletzung, Codedopplung, schlechte Lesbarkeit sollte behoben werden |
| **OPTIONAL** | Vereinfachung ohne funktionalen Einfluss kann behoben werden | | **OPTIONAL** | Vereinfachung ohne funktionalen Einfluss kann behoben werden |
--- ---

86
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,86 @@
name: CI Swift Tests (macOS)
on:
workflow_dispatch:
jobs:
test:
name: Swift Tests (macOS)
runs-on: macos-15
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
defaults:
run:
working-directory: Migration
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Select Xcode 16.3
run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
- name: Show Swift version
run: swift --version
- name: Install SwiftLint
run: brew install swiftlint
- name: Run SwiftLint
run: swiftlint lint --reporter github-actions-logging
- name: Build
run: swift build 2>&1 | tee build.log; exit ${PIPESTATUS[0]}
- name: Run tests
run: swift test 2>&1 | tee test.log; exit ${PIPESTATUS[0]}
- name: Start MockServer
run: |
cd "$GITHUB_WORKSPACE/MockServer"
npm install --silent
node server.js &
echo "MOCK_PID=$!" >> "$GITHUB_ENV"
sleep 2
curl -sf http://localhost:3000/auth/login \
-X POST -H 'Content-Type: application/json' \
-d '{"username":"ci","password":"ci"}' > /dev/null
echo "MockServer running on :3000"
- name: Run integration tests
env:
MOCKSERVER_INTEGRATION: "1"
run: swift test --filter MockServerIntegration 2>&1 | tee integration.log; exit ${PIPESTATUS[0]}
- name: Stop MockServer
if: always()
run: kill $MOCK_PID 2>/dev/null || true
demonstrator:
name: Demonstrator Build + Test (macOS)
runs-on: macos-15
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
defaults:
run:
working-directory: Demonstrator/App
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Select Xcode 16.3
run: sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer
- name: Show Swift version
run: swift --version
- name: Build
run: swift build 2>&1 | tee build.log; exit ${PIPESTATUS[0]}
- name: Run tests
run: swift test 2>&1 | tee test.log; exit ${PIPESTATUS[0]}

View file

@ -19,7 +19,7 @@ android {
versionName = "1.3" versionName = "1.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("boolean", "FEATURE_CAMERA_ENABLED", "true") buildConfigField("boolean", "FEATURE_CAMERA_ENABLED", "false")
} }
buildTypes { buildTypes {

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -17,6 +18,7 @@ import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -37,6 +39,7 @@ import de.bollwerk.app.ui.inventory.InventoryPickerViewModel
import de.bollwerk.app.ui.navigation.BollwerkNavGraph import de.bollwerk.app.ui.navigation.BollwerkNavGraph
import de.bollwerk.app.ui.navigation.TopLevelDestination import de.bollwerk.app.ui.navigation.TopLevelDestination
import de.bollwerk.app.ui.update.UpdateBanner import de.bollwerk.app.ui.update.UpdateBanner
import de.bollwerk.app.ui.update.UpdateStatus
import de.bollwerk.app.ui.update.UpdateViewModel import de.bollwerk.app.ui.update.UpdateViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -142,6 +145,25 @@ internal fun MainScreen() {
} }
} }
if (updateState.status is UpdateStatus.Available) {
val available = updateState.status as UpdateStatus.Available
AlertDialog(
onDismissRequest = updateViewModel::dismiss,
title = { Text("Update verf\u00fcgbar") },
text = { Text("Version ${available.versionName} ist verf\u00fcgbar. Jetzt aktualisieren?") },
confirmButton = {
TextButton(onClick = updateViewModel::startDownload) {
Text("Aktualisieren")
}
},
dismissButton = {
TextButton(onClick = updateViewModel::dismiss) {
Text("Sp\u00e4ter")
}
}
)
}
if (isInventoryPickerVisible) { if (isInventoryPickerVisible) {
InventoryPickerSheet( InventoryPickerSheet(
onDismiss = { isInventoryPickerVisible = false }, onDismiss = { isInventoryPickerVisible = false },

View file

@ -32,7 +32,7 @@ internal fun UpdateBanner(
onDismiss: () -> Unit, onDismiss: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val isVisible = status !is UpdateStatus.Hidden && status !is UpdateStatus.Checking val isVisible = status !is UpdateStatus.Hidden && status !is UpdateStatus.Checking && status !is UpdateStatus.Available
AnimatedVisibility( AnimatedVisibility(
visible = isVisible, visible = isVisible,

View file

@ -93,5 +93,8 @@ internal fun Application.configureRouting(
// Admin web UI (static) // Admin web UI (static)
staticResources("/admin", "static/admin") staticResources("/admin", "static/admin")
// Classpath assets (logo etc.)
staticResources("/res", "static")
} }
} }

View file

@ -248,11 +248,11 @@ private fun buildHomepageHtml(versionName: String, versionCode: Int, apkUrl: Str
<div class="panel"> <div class="panel">
<span class="rivet-bl"></span> <span class="rivet-bl"></span>
<span class="rivet-br"></span> <span class="rivet-br"></span>
<img src="/static/logo.png" alt="Bollwerk" class="logo"> <img src="/res/logo.png" alt="Bollwerk" class="logo">
<h1>Bollwerk</h1> <h1>Bollwerk</h1>
<p class="subtitle">Inventar Vorsorge Sicherheit</p> <p class="subtitle">Inventar Vorsorge Sicherheit</p>
<div class="rust-line"></div> <div class="rust-line"></div>
<p class="version">V.<span>$versionName</span> // Build <span>$versionCode</span></p> <p class="version">v<span>$versionName</span></p>
<div id="qrcode"></div> <div id="qrcode"></div>
<a href="${apkUrl.replace("\"", "&quot;")}" class="download-link"> APK herunterladen</a> <a href="${apkUrl.replace("\"", "&quot;")}" class="download-link"> APK herunterladen</a>
<p class="hint">QR-Code scannen oder Link antippen, um die App zu installieren.</p> <p class="hint">QR-Code scannen oder Link antippen, um die App zu installieren.</p>

View file

@ -110,7 +110,6 @@ class VersionEndpointTest {
// Then // Then
assertTrue(body.contains("2.1.0")) assertTrue(body.contains("2.1.0"))
assertTrue(body.contains("42"))
} }
@Test @Test