chore: update generaltest_admin_message.cpython-313.pyc, receive_admin_messages.cpython-313.pyc, send_admin_messages.cpython-313.pyc
This commit is contained in:
parent
33c7ddb9ab
commit
06fa017c04
8 changed files with 253 additions and 3 deletions
BIN
.github/skills/admin-message-e2ee/__pycache__/generaltest_admin_message.cpython-313.pyc
vendored
Normal file
BIN
.github/skills/admin-message-e2ee/__pycache__/generaltest_admin_message.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
.github/skills/admin-message-e2ee/__pycache__/receive_admin_messages.cpython-313.pyc
vendored
Normal file
BIN
.github/skills/admin-message-e2ee/__pycache__/receive_admin_messages.cpython-313.pyc
vendored
Normal file
Binary file not shown.
BIN
.github/skills/admin-message-e2ee/__pycache__/send_admin_messages.cpython-313.pyc
vendored
Normal file
BIN
.github/skills/admin-message-e2ee/__pycache__/send_admin_messages.cpython-313.pyc
vendored
Normal file
Binary file not shown.
22
.github/skills/android-device/SKILL.md
vendored
22
.github/skills/android-device/SKILL.md
vendored
|
|
@ -88,6 +88,28 @@ $adb = "C:\Users\JensR\AppData\Local\Android\Sdk\platform-tools\adb.exe"
|
||||||
|
|
||||||
**Hinweis:** Die Skript-Aktionen `deploy-device` und `install-device` verwenden `adb -d` und funktionieren nur über USB. Bei Wireless ADB die manuellen Kommandos verwenden.
|
**Hinweis:** Die Skript-Aktionen `deploy-device` und `install-device` verwenden `adb -d` und funktionieren nur über USB. Bei Wireless ADB die manuellen Kommandos verwenden.
|
||||||
|
|
||||||
|
### Robustes Python-Deployment (empfohlen)
|
||||||
|
|
||||||
|
Neues Skript: `.github/skills/android-device/deploy-device.py`
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Standard: Build + Device erkennen + Install + Launch
|
||||||
|
python ".github/skills/android-device/deploy-device.py" --repo-root "X:\bollwerk"
|
||||||
|
|
||||||
|
# Explizites Wireless-Geraet
|
||||||
|
python ".github/skills/android-device/deploy-device.py" --repo-root "X:\bollwerk" --serial "192.168.68.107:42539"
|
||||||
|
|
||||||
|
# Ohne Build (wenn APK schon gebaut), nur Install
|
||||||
|
python ".github/skills/android-device/deploy-device.py" --repo-root "X:\bollwerk" --skip-build --no-launch
|
||||||
|
```
|
||||||
|
|
||||||
|
Funktionen:
|
||||||
|
|
||||||
|
- ADB-Autodetect ueber `--adb`, `ANDROID_SDK_ROOT`/`ANDROID_HOME` oder `local.properties`
|
||||||
|
- Robuste Device-Erkennung (USB oder Wireless)
|
||||||
|
- Install-Retries mit klaren Fehlern
|
||||||
|
- Definierte Exitcodes (`0` Erfolg, `1` Fehler)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# App bauen + auf Gerät installieren + starten (nur USB)
|
# App bauen + auf Gerät installieren + starten (nur USB)
|
||||||
& ".github/skills/android-build/android-dev.ps1" -Action deploy-device
|
& ".github/skills/android-build/android-dev.ps1" -Action deploy-device
|
||||||
|
|
|
||||||
228
.github/skills/android-device/deploy-device.py
vendored
Normal file
228
.github/skills/android-device/deploy-device.py
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Robustes Deployment der Debug-APK auf ein physisches Android-Geraet.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- ADB automatisch finden (Argument, ENV, local.properties)
|
||||||
|
- USB oder Wireless-Device robust erkennen
|
||||||
|
- Optional Build via Gradle
|
||||||
|
- Install mit Retry
|
||||||
|
- Optional App-Start
|
||||||
|
- Klare Exitcodes und kompakte Logs
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
PACKAGE_NAME = "de.bollwerk.app"
|
||||||
|
MAIN_ACTIVITY = "de.bollwerk.app/.MainActivity"
|
||||||
|
DEFAULT_APK_REL = Path("app/build/outputs/apk/debug/app-debug.apk")
|
||||||
|
DEFAULT_GRADLEW = Path("gradlew.bat")
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg: str) -> None:
|
||||||
|
print(msg, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd: List[str], cwd: Optional[Path] = None, timeout: Optional[int] = None) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run(
|
||||||
|
cmd,
|
||||||
|
cwd=str(cwd) if cwd else None,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=timeout,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_sdk_dir(raw: str) -> str:
|
||||||
|
# local.properties encodes ':' as '\:' on Windows.
|
||||||
|
val = raw.strip()
|
||||||
|
val = val.replace("\\:", ":")
|
||||||
|
val = val.replace("\\\\", "\\")
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def read_sdk_from_local_properties(repo_root: Path) -> Optional[Path]:
|
||||||
|
local_props = repo_root / "local.properties"
|
||||||
|
if not local_props.exists():
|
||||||
|
return None
|
||||||
|
for line in local_props.read_text(encoding="utf-8").splitlines():
|
||||||
|
if line.strip().startswith("sdk.dir="):
|
||||||
|
_, value = line.split("=", 1)
|
||||||
|
sdk = normalize_sdk_dir(value)
|
||||||
|
p = Path(sdk)
|
||||||
|
return p if p.exists() else None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_adb(repo_root: Path, explicit_adb: Optional[str]) -> Path:
|
||||||
|
candidates: List[Path] = []
|
||||||
|
|
||||||
|
if explicit_adb:
|
||||||
|
candidates.append(Path(explicit_adb))
|
||||||
|
|
||||||
|
env_android_sdk = os.environ.get("ANDROID_SDK_ROOT") or os.environ.get("ANDROID_HOME")
|
||||||
|
if env_android_sdk:
|
||||||
|
candidates.append(Path(env_android_sdk) / "platform-tools" / "adb.exe")
|
||||||
|
candidates.append(Path(env_android_sdk) / "platform-tools" / "adb")
|
||||||
|
|
||||||
|
sdk_from_local = read_sdk_from_local_properties(repo_root)
|
||||||
|
if sdk_from_local:
|
||||||
|
candidates.append(sdk_from_local / "platform-tools" / "adb.exe")
|
||||||
|
candidates.append(sdk_from_local / "platform-tools" / "adb")
|
||||||
|
|
||||||
|
# Last fallback: rely on PATH.
|
||||||
|
path_adb = shutil_which("adb")
|
||||||
|
if path_adb:
|
||||||
|
candidates.append(Path(path_adb))
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
if candidate.exists():
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"ADB nicht gefunden. Setze --adb oder ANDROID_SDK_ROOT/ANDROID_HOME oder local.properties sdk.dir."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def shutil_which(binary: str) -> Optional[str]:
|
||||||
|
for folder in os.environ.get("PATH", "").split(os.pathsep):
|
||||||
|
p = Path(folder) / binary
|
||||||
|
if p.exists():
|
||||||
|
return str(p)
|
||||||
|
if os.name == "nt":
|
||||||
|
p_exe = Path(folder) / f"{binary}.exe"
|
||||||
|
if p_exe.exists():
|
||||||
|
return str(p_exe)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_adb_devices(output: str) -> List[Tuple[str, str]]:
|
||||||
|
devices: List[Tuple[str, str]] = []
|
||||||
|
for line in output.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("List of devices"):
|
||||||
|
continue
|
||||||
|
# serial <tab> state [extra]
|
||||||
|
parts = re.split(r"\s+", line)
|
||||||
|
if len(parts) >= 2:
|
||||||
|
devices.append((parts[0], parts[1]))
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_target_serial(adb: Path, prefer_serial: Optional[str], prefer_usb: bool, timeout_sec: int) -> str:
|
||||||
|
start = time.time()
|
||||||
|
while True:
|
||||||
|
cp = run_cmd([str(adb), "devices", "-l"])
|
||||||
|
if cp.returncode == 0:
|
||||||
|
devices = parse_adb_devices(cp.stdout)
|
||||||
|
|
||||||
|
if prefer_serial:
|
||||||
|
for serial, state in devices:
|
||||||
|
if serial == prefer_serial and state == "device":
|
||||||
|
return serial
|
||||||
|
else:
|
||||||
|
live = [(s, st) for s, st in devices if st == "device"]
|
||||||
|
if prefer_usb:
|
||||||
|
usb = [s for s, _ in live if not re.match(r"\d+\.\d+\.\d+\.\d+:\d+", s)]
|
||||||
|
if usb:
|
||||||
|
return usb[0]
|
||||||
|
if live:
|
||||||
|
return live[0][0]
|
||||||
|
|
||||||
|
if (time.time() - start) >= timeout_sec:
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
raise RuntimeError("Kein geeignetes ADB-Geraet gefunden (USB/Wireless, Debugging, Autorisierung pruefen).")
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_apk(repo_root: Path, apk_path: Path, gradlew_path: Path, skip_build: bool) -> None:
|
||||||
|
if skip_build and apk_path.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not gradlew_path.exists():
|
||||||
|
raise FileNotFoundError(f"Gradle Wrapper nicht gefunden: {gradlew_path}")
|
||||||
|
|
||||||
|
log("[1/4] Build Debug-APK ...")
|
||||||
|
cp = run_cmd([str(gradlew_path), "assembleDebug"], cwd=repo_root)
|
||||||
|
if cp.returncode != 0:
|
||||||
|
tail = "\n".join((cp.stdout + "\n" + cp.stderr).splitlines()[-40:])
|
||||||
|
raise RuntimeError(f"Gradle Build fehlgeschlagen.\n{tail}")
|
||||||
|
|
||||||
|
if not apk_path.exists():
|
||||||
|
raise FileNotFoundError(f"APK nach Build nicht gefunden: {apk_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def adb_install(adb: Path, serial: str, apk_path: Path, retries: int) -> None:
|
||||||
|
last_err = ""
|
||||||
|
for attempt in range(1, retries + 1):
|
||||||
|
log(f"[3/4] Installiere APK (Versuch {attempt}/{retries}) ...")
|
||||||
|
cp = run_cmd([str(adb), "-s", serial, "install", "-r", str(apk_path)], timeout=180)
|
||||||
|
out = (cp.stdout + "\n" + cp.stderr).strip()
|
||||||
|
if cp.returncode == 0 and "Success" in out:
|
||||||
|
log("Install OK")
|
||||||
|
return
|
||||||
|
last_err = out
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
raise RuntimeError(f"APK-Installation fehlgeschlagen.\n{last_err}")
|
||||||
|
|
||||||
|
|
||||||
|
def adb_launch(adb: Path, serial: str) -> None:
|
||||||
|
cp = run_cmd([str(adb), "-s", serial, "shell", "am", "start", "-n", MAIN_ACTIVITY], timeout=60)
|
||||||
|
out = (cp.stdout + "\n" + cp.stderr).strip()
|
||||||
|
if cp.returncode != 0 or "Error" in out or "Exception" in out:
|
||||||
|
raise RuntimeError(f"App-Start fehlgeschlagen.\n{out}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Deploy app-debug.apk robust auf ein physisches Android-Geraet")
|
||||||
|
parser.add_argument("--repo-root", default=".", help="Pfad zum Repo-Root (Standard: aktuelles Verzeichnis)")
|
||||||
|
parser.add_argument("--adb", default=None, help="Expliziter Pfad zu adb(.exe)")
|
||||||
|
parser.add_argument("--serial", default=None, help="Geraete-Serial explizit (z. B. USB-Serial oder 192.168.x.x:port)")
|
||||||
|
parser.add_argument("--prefer-usb", action="store_true", help="Bevorzuge USB-Geraet, wenn --serial nicht gesetzt")
|
||||||
|
parser.add_argument("--timeout-device", type=int, default=30, help="Maximale Wartezeit auf Device-Erkennung in Sekunden")
|
||||||
|
parser.add_argument("--retries-install", type=int, default=3, help="Install-Retries")
|
||||||
|
parser.add_argument("--skip-build", action="store_true", help="Build ueberspringen, wenn APK bereits existiert")
|
||||||
|
parser.add_argument("--no-launch", action="store_true", help="App nach Installation nicht starten")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
repo_root = Path(args.repo_root).resolve()
|
||||||
|
apk_path = (repo_root / DEFAULT_APK_REL).resolve()
|
||||||
|
gradlew_path = (repo_root / DEFAULT_GRADLEW).resolve()
|
||||||
|
|
||||||
|
try:
|
||||||
|
adb = find_adb(repo_root, args.adb)
|
||||||
|
log(f"ADB: {adb}")
|
||||||
|
|
||||||
|
ensure_apk(repo_root, apk_path, gradlew_path, skip_build=args.skip_build)
|
||||||
|
|
||||||
|
log("[2/4] Suche Device ...")
|
||||||
|
serial = resolve_target_serial(adb, args.serial, args.prefer_usb, args.timeout_device)
|
||||||
|
log(f"Device: {serial}")
|
||||||
|
|
||||||
|
adb_install(adb, serial, apk_path, retries=args.retries_install)
|
||||||
|
|
||||||
|
if not args.no_launch:
|
||||||
|
log("[4/4] Starte App ...")
|
||||||
|
adb_launch(adb, serial)
|
||||||
|
log("Launch OK")
|
||||||
|
|
||||||
|
log("DEPLOY SUCCESS")
|
||||||
|
return 0
|
||||||
|
except Exception as exc:
|
||||||
|
log(f"DEPLOY FAILED: {exc}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
BIN
.github/skills/publish/__pycache__/publish-apk.cpython-313.pyc
vendored
Normal file
BIN
.github/skills/publish/__pycache__/publish-apk.cpython-313.pyc
vendored
Normal file
Binary file not shown.
|
|
@ -438,7 +438,7 @@ internal fun SettingsScreen(
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Version ${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}",
|
text = "Version ${BuildConfig.VERSION_NAME}",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ internal fun UpdateBanner(
|
||||||
) {
|
) {
|
||||||
when (status) {
|
when (status) {
|
||||||
is UpdateStatus.Available -> AvailableBanner(
|
is UpdateStatus.Available -> AvailableBanner(
|
||||||
versionName = "${status.versionName}.${status.versionCode}",
|
versionName = status.versionName,
|
||||||
onDownloadClick = onDownloadClick,
|
onDownloadClick = onDownloadClick,
|
||||||
onDismiss = onDismiss
|
onDismiss = onDismiss
|
||||||
)
|
)
|
||||||
|
|
@ -55,7 +55,7 @@ internal fun UpdateBanner(
|
||||||
progress = status.progress
|
progress = status.progress
|
||||||
)
|
)
|
||||||
is UpdateStatus.ReadyToInstall -> ReadyBanner(
|
is UpdateStatus.ReadyToInstall -> ReadyBanner(
|
||||||
versionName = "${status.versionName}.${status.versionCode}",
|
versionName = status.versionName,
|
||||||
onDismiss = onDismiss
|
onDismiss = onDismiss
|
||||||
)
|
)
|
||||||
is UpdateStatus.Error -> ErrorBanner(
|
is UpdateStatus.Error -> ErrorBanner(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue