diff --git a/.gitignore b/.gitignore index 9370b08..d3b687d 100644 --- a/.gitignore +++ b/.gitignore @@ -362,3 +362,9 @@ android/fastlane/README.md.bak # Screenshots unter materials/screenshots/. Nur die leeren Ordner (.gitkeep) # bleiben eingecheckt, damit supply die Struktur vorfindet. android/fastlane/metadata/android/**/images/**/*.png + +# Fastlane (App-Store-Upload) +ios/fastlane/report.xml +ios/fastlane/Preview.html +ios/fastlane/app-store-connect-api-key.json +ios/fastlane/screenshots/**/*.jpg diff --git a/integration_test/screenshot_test.dart b/integration_test/screenshot_test.dart index d467b96..8f40fb8 100644 --- a/integration_test/screenshot_test.dart +++ b/integration_test/screenshot_test.dart @@ -17,7 +17,8 @@ import 'package:marianum_mobile/view/pages/talk/widgets/chat_tile.dart'; /// Stundenplan (hell + dunkel), Talk-Liste, Talk-Chat, Dateien, „Mehr"-Bereich /// und Einstellungen. Ausgeführt über `flutter drive` mit /// test_driver/integration_test.dart, orchestriert von tool/screenshots.sh -/// (Phone / 7″ / 10″). +/// (Android: Phone / 7″ / 10″) bzw. tool/screenshots_ios.sh (iPhone 6,5″ / +/// iPad 13″). /// /// Kein Netzwerk: der Demo-Modus beantwortet allen Backend-Verkehr aus /// Fixtures, daher ist der Lauf deterministisch. @@ -42,11 +43,13 @@ Future main() async { _log('Login'); await _login(tester); + // Großzügig: auf einem frisch angelegten iOS-Simulator dauert der erste + // Post-Login-Aufbau deutlich länger als 20s. _log('warte auf App-Shell'); await _pumpUntil( tester, find.byType(App), - timeout: const Duration(seconds: 20), + timeout: const Duration(seconds: 60), ); _log('warte auf Post-Login-Splash-Ende'); await _pumpUntilGone( @@ -64,6 +67,12 @@ Future main() async { await binding.convertFlutterSurfaceToImage(); } + // Den einmaligen Benachrichtigungs-Dialog beim ersten Talk-Besuch + // unterdrücken. Auf Android erteilt tool/screenshots.sh die Permission + // vorab, auf iOS lässt sie sich im Simulator nicht vorab erteilen – der + // Dialog würde sonst über der Talk-Liste liegen. + _markTalkPermissionPromptShown(tester); + // 1 + 2: Stundenplan in beiden Themes. await _goTo(tester, Modules.timetable); _setTheme(tester, ThemeMode.light); @@ -129,6 +138,16 @@ void _setTheme(WidgetTester tester, ThemeMode mode) { context.read().val(write: true).appTheme = mode; } +void _markTalkPermissionPromptShown(WidgetTester tester) { + final context = tester.element(find.byType(App)); + context + .read() + .val(write: true) + .notificationSettings + .talkPermissionPromptShown = + true; +} + /// Navigiert zu [module] – über den Bottom-Tab, wenn er in der Leiste liegt, /// sonst als Vollbild-Push. Größenunabhängig, da die Tab-Anzahl je Gerät variiert. Future _goTo(WidgetTester tester, Modules module) async { diff --git a/ios/fastlane/Appfile b/ios/fastlane/Appfile new file mode 100644 index 0000000..4748c04 --- /dev/null +++ b/ios/fastlane/Appfile @@ -0,0 +1,2 @@ +app_identifier("eu.mhsl.marianum.mobile.client") +team_id("MY55VF3KPG") diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile new file mode 100644 index 0000000..3a1f43a --- /dev/null +++ b/ios/fastlane/Fastfile @@ -0,0 +1,27 @@ +default_platform(:ios) + +# App-Store-Auslieferung über fastlane deliver. Die Screenshots erzeugt +# ../tool/screenshots_ios.sh (Demo-Login + integration_test) direkt in +# screenshots/de-DE/; deliver ordnet sie anhand der Pixelgröße iPhone 6,5″ bzw. +# iPad 13″ zu. +# +# Authentifizierung über einen App-Store-Connect-API-Key (JSON im fastlane- +# Format, siehe README.md). Pfad per APP_STORE_CONNECT_API_KEY setzen; die Datei +# selbst wird per .gitignore nicht eingecheckt. +platform :ios do + desc "Nur Screenshots hochladen (ersetzt die vorhandenen, verändert keine Texte/Binaries)" + lane :upload_screenshots do + upload_to_app_store( + api_key_path: ENV["APP_STORE_CONNECT_API_KEY"] || "fastlane/app-store-connect-api-key.json", + skip_binary_upload: true, + skip_metadata: true, + skip_app_version_update: true, + skip_screenshots: false, + overwrite_screenshots: true, + screenshots_path: "./fastlane/screenshots", + precheck_include_in_app_purchases: false, + run_precheck_before_submit: false, + force: true, # kein HTML-Vorschau-Bestätigungsschritt + ) + end +end diff --git a/ios/fastlane/README.md b/ios/fastlane/README.md new file mode 100644 index 0000000..39dc5c5 --- /dev/null +++ b/ios/fastlane/README.md @@ -0,0 +1,49 @@ +# Fastlane – App-Store-Screenshots & Upload + +iOS-Gegenstück zu `android/fastlane`. Die App muss dafür **nicht** neu gebaut +werden – der Demo-Modus (`demo@`-Login) liefert die kompletten Inhalte aus +Fixtures. + +## 1. Screenshots aufnehmen + +Aus dem **Client**-Wurzelverzeichnis: + +```bash +tool/screenshots_ios.sh +``` + +Fährt „iPhone 14 Plus“ (6,5″, 1284×2778) und „iPad Pro 13-inch (M5)“ +(13″, 2064×2752) nacheinander durch und legt die PNGs in +`materials/screenshots/{ios/6-5-inch,ipados/13-inch}/` sowie als JPEG-Upload-Kopie +(App Store Connect lehnt Alpha-Kanäle ab) in `screenshots/de-DE/` ab. Fehlende +Simulatoren legt das Skript selbst an. + +- Nur eine Größe: `PROFILES="iphone" tool/screenshots_ios.sh` +- Andere Simulatoren: `IPHONE_SIM="iPhone 11 Pro Max" IPAD_SIM="…" tool/screenshots_ios.sh` + +App und Schlüsselbund der verwendeten Simulatoren werden dabei zurückgesetzt. + +## 2. Hochladen + +fastlane installieren (`brew install fastlane`), in App Store Connect unter +*Benutzer und Zugriff → Integrationen → App Store Connect API* einen Key mit +Rolle *App-Manager* anlegen und als JSON ablegen: + +```json +{ + "key_id": "ABC123XYZ", + "issuer_id": "00000000-0000-0000-0000-000000000000", + "key": "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----", + "in_house": false +} +``` + +```bash +export APP_STORE_CONNECT_API_KEY=/pfad/zu/app-store-connect-api-key.json +cd ios +fastlane upload_screenshots +``` + +Die Screenshots landen in der aktuell bearbeitbaren App-Store-Version +(„Vorbereitung für Einreichung“); vorhandene Screenshots werden ersetzt. Der +API-Key wird **nicht** eingecheckt (siehe `.gitignore`). diff --git a/ios/fastlane/screenshots/de-DE/.gitkeep b/ios/fastlane/screenshots/de-DE/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/api/demo/demo_marianumconnect.dart b/lib/api/demo/demo_marianumconnect.dart index ce3faaf..64c78ee 100644 --- a/lib/api/demo/demo_marianumconnect.dart +++ b/lib/api/demo/demo_marianumconnect.dart @@ -32,6 +32,8 @@ class DemoMarianumConnect { return DemoTimetable.timegrid().result.map((e) => e.toJson()).toList(); case 'timetable/holidays': return DemoTimetable.holidays().result.map((e) => e.toJson()).toList(); + case 'timetable/custom-events': + return DemoTimetable.customEvents().toJson(); case 'holidays': return DemoHolidays.upcoming().map((e) => e.toJson()).toList(); case 'users/search': diff --git a/materials/screenshots/ios/6-5-inch/1.jpg b/materials/screenshots/ios/6-5-inch/1.jpg deleted file mode 100644 index c3b23eb..0000000 Binary files a/materials/screenshots/ios/6-5-inch/1.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/1_stundenplan_hell.png b/materials/screenshots/ios/6-5-inch/1_stundenplan_hell.png new file mode 100644 index 0000000..bc8f3f3 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/1_stundenplan_hell.png differ diff --git a/materials/screenshots/ios/6-5-inch/2_stundenplan_dunkel.png b/materials/screenshots/ios/6-5-inch/2_stundenplan_dunkel.png new file mode 100644 index 0000000..66da833 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/2_stundenplan_dunkel.png differ diff --git a/materials/screenshots/ios/6-5-inch/3.jpg b/materials/screenshots/ios/6-5-inch/3.jpg deleted file mode 100644 index 823f234..0000000 Binary files a/materials/screenshots/ios/6-5-inch/3.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/3_talk_liste.png b/materials/screenshots/ios/6-5-inch/3_talk_liste.png new file mode 100644 index 0000000..77daad7 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/3_talk_liste.png differ diff --git a/materials/screenshots/ios/6-5-inch/4.jpg b/materials/screenshots/ios/6-5-inch/4.jpg deleted file mode 100644 index c5e36fd..0000000 Binary files a/materials/screenshots/ios/6-5-inch/4.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/4_talk_chat.png b/materials/screenshots/ios/6-5-inch/4_talk_chat.png new file mode 100644 index 0000000..b067679 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/4_talk_chat.png differ diff --git a/materials/screenshots/ios/6-5-inch/5.jpg b/materials/screenshots/ios/6-5-inch/5.jpg deleted file mode 100644 index 20c5fe8..0000000 Binary files a/materials/screenshots/ios/6-5-inch/5.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/5_dateien.png b/materials/screenshots/ios/6-5-inch/5_dateien.png new file mode 100644 index 0000000..44594c8 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/5_dateien.png differ diff --git a/materials/screenshots/ios/6-5-inch/6.jpg b/materials/screenshots/ios/6-5-inch/6.jpg deleted file mode 100644 index 56b56f6..0000000 Binary files a/materials/screenshots/ios/6-5-inch/6.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/6_mehr.png b/materials/screenshots/ios/6-5-inch/6_mehr.png new file mode 100644 index 0000000..00b7e53 Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/6_mehr.png differ diff --git a/materials/screenshots/ios/6-5-inch/7.jpg b/materials/screenshots/ios/6-5-inch/7.jpg deleted file mode 100644 index 3b6f34d..0000000 Binary files a/materials/screenshots/ios/6-5-inch/7.jpg and /dev/null differ diff --git a/materials/screenshots/ios/6-5-inch/7_einstellungen.png b/materials/screenshots/ios/6-5-inch/7_einstellungen.png new file mode 100644 index 0000000..6e03b0f Binary files /dev/null and b/materials/screenshots/ios/6-5-inch/7_einstellungen.png differ diff --git a/materials/screenshots/ios/6-5-inch/8.jpg b/materials/screenshots/ios/6-5-inch/8.jpg deleted file mode 100644 index d2e4c39..0000000 Binary files a/materials/screenshots/ios/6-5-inch/8.jpg and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/1.png b/materials/screenshots/ipados/13-inch/1.png deleted file mode 100644 index 0180ad5..0000000 Binary files a/materials/screenshots/ipados/13-inch/1.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/1_stundenplan_hell.png b/materials/screenshots/ipados/13-inch/1_stundenplan_hell.png new file mode 100644 index 0000000..feb2396 Binary files /dev/null and b/materials/screenshots/ipados/13-inch/1_stundenplan_hell.png differ diff --git a/materials/screenshots/ipados/13-inch/2.png b/materials/screenshots/ipados/13-inch/2.png deleted file mode 100644 index 7d99b6c..0000000 Binary files a/materials/screenshots/ipados/13-inch/2.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/2_stundenplan_dunkel.png b/materials/screenshots/ipados/13-inch/2_stundenplan_dunkel.png new file mode 100644 index 0000000..c64e619 Binary files /dev/null and b/materials/screenshots/ipados/13-inch/2_stundenplan_dunkel.png differ diff --git a/materials/screenshots/ipados/13-inch/3.png b/materials/screenshots/ipados/13-inch/3.png deleted file mode 100644 index e55304a..0000000 Binary files a/materials/screenshots/ipados/13-inch/3.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/3_talk_liste.png b/materials/screenshots/ipados/13-inch/3_talk_liste.png new file mode 100644 index 0000000..f825cba Binary files /dev/null and b/materials/screenshots/ipados/13-inch/3_talk_liste.png differ diff --git a/materials/screenshots/ipados/13-inch/4.png b/materials/screenshots/ipados/13-inch/4.png deleted file mode 100644 index 9ddddd8..0000000 Binary files a/materials/screenshots/ipados/13-inch/4.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/4_talk_chat.png b/materials/screenshots/ipados/13-inch/4_talk_chat.png new file mode 100644 index 0000000..44b3240 Binary files /dev/null and b/materials/screenshots/ipados/13-inch/4_talk_chat.png differ diff --git a/materials/screenshots/ipados/13-inch/5.png b/materials/screenshots/ipados/13-inch/5.png deleted file mode 100644 index e417ef6..0000000 Binary files a/materials/screenshots/ipados/13-inch/5.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/5_dateien.png b/materials/screenshots/ipados/13-inch/5_dateien.png new file mode 100644 index 0000000..f55ea5f Binary files /dev/null and b/materials/screenshots/ipados/13-inch/5_dateien.png differ diff --git a/materials/screenshots/ipados/13-inch/6.png b/materials/screenshots/ipados/13-inch/6.png deleted file mode 100644 index 0b8316f..0000000 Binary files a/materials/screenshots/ipados/13-inch/6.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/6_mehr.png b/materials/screenshots/ipados/13-inch/6_mehr.png new file mode 100644 index 0000000..9ec69e4 Binary files /dev/null and b/materials/screenshots/ipados/13-inch/6_mehr.png differ diff --git a/materials/screenshots/ipados/13-inch/7.png b/materials/screenshots/ipados/13-inch/7.png deleted file mode 100644 index 6ac79c2..0000000 Binary files a/materials/screenshots/ipados/13-inch/7.png and /dev/null differ diff --git a/materials/screenshots/ipados/13-inch/7_einstellungen.png b/materials/screenshots/ipados/13-inch/7_einstellungen.png new file mode 100644 index 0000000..7604b7f Binary files /dev/null and b/materials/screenshots/ipados/13-inch/7_einstellungen.png differ diff --git a/materials/screenshots/ipados/13-inch/8.png b/materials/screenshots/ipados/13-inch/8.png deleted file mode 100644 index 2ae5108..0000000 Binary files a/materials/screenshots/ipados/13-inch/8.png and /dev/null differ diff --git a/test/demo/demo_marianumconnect_test.dart b/test/demo/demo_marianumconnect_test.dart index 80ff6e6..d5a9a53 100644 --- a/test/demo/demo_marianumconnect_test.dart +++ b/test/demo/demo_marianumconnect_test.dart @@ -8,6 +8,7 @@ import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_school import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/timetable_get_week/timetable_get_week_response.dart'; +import 'package:marianum_mobile/api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart'; /// The demo interceptor answers MarianumConnect calls with these bodies; each /// must round-trip through the same parser the real query uses. A shape @@ -57,6 +58,17 @@ void main() { ); }); + // Eigener Case nötig: sonst greift der timetable//-Fallback und + // liefert eine Woche statt `{events: [...]}` (iOS-Widget-Refresh crashte). + test('timetable/custom-events', () { + expect( + () => GetCustomTimetableEventResponse.fromJson( + asMap('timetable/custom-events'), + ), + returnsNormally, + ); + }); + test('holidays → non-empty list of McHoliday', () { final holidays = asList('holidays').map(McHoliday.fromJson).toList(); expect(holidays, isNotEmpty); diff --git a/tool/screenshots_ios.sh b/tool/screenshots_ios.sh new file mode 100755 index 0000000..18788bb --- /dev/null +++ b/tool/screenshots_ios.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# +# App-Store-Screenshots über den Demo-Login aufnehmen – iOS-Gegenstück zu +# tool/screenshots.sh. +# +# Fährt denselben integration_test-Lauf (integration_test/screenshot_test.dart) +# per flutter drive auf je einem iOS-Simulator pro Formfaktor: iPhone 6,5″ +# (iPhone 14 Plus, 1284×2778) und iPad 13″ (iPad Pro 13-inch, 2064×2752). Die +# Simulatoren liefern die Frames direkt in der Zielauflösung, eine +# Größenumstellung wie per `adb shell wm size` ist nicht nötig. Fehlt ein +# Simulator, wird er mit gleichnamigem Gerätetyp angelegt. +# +# Ablage: +# - kanonisch (versioniert): materials/screenshots/{ios/6-5-inch,ipados/13-inch}/ +# - Kopie für den Upload: ios/fastlane/screenshots/de-DE/_.jpg +# (deliver ordnet die Bilder anhand der Pixelgröße dem Gerät zu; das Präfix +# verhindert nur Namenskollisionen zwischen iPhone und iPad.) +# Danach: `cd ios && fastlane upload_screenshots`. +# +# Voraussetzungen: +# - Xcode mit iOS-Simulator-Runtime; Simulatoren (Namen per IPHONE_SIM/IPAD_SIM +# überschreibbar; Name = Gerätetyp aus `xcrun simctl list devicetypes`) +# - flutter im PATH +# +# Achtung: Pro Simulator werden App und Schlüsselbund zurückgesetzt, damit der +# Lauf garantiert mit dem Demo-Login startet und keine echte Sitzung zeigt. +# Deshalb eigene Simulatoren verwenden, nicht den für die Entwicklung. +# +# Aufruf (vom Client-Projektwurzelverzeichnis): +# tool/screenshots_ios.sh # debug-Build +# BUILD_MODE=profile tool/screenshots_ios.sh +# PROFILES="iphone" tool/screenshots_ios.sh # nur eine Größe + +set -euo pipefail + +cd "$(dirname "$0")/.." + +BUILD_MODE="${BUILD_MODE:-debug}" +FASTLANE_DIR="ios/fastlane/screenshots/de-DE" +BUNDLE_ID="eu.mhsl.marianum.mobile.client" +IPHONE_SIM="${IPHONE_SIM:-iPhone 14 Plus}" +IPAD_SIM="${IPAD_SIM:-iPad Pro 13-inch (M5)}" +PROFILES="${PROFILES:-iphone ipad}" + +# UDID des ersten verfügbaren Simulators mit exakt diesem Namen. +sim_udid() { + xcrun simctl list devices available -j | + /usr/bin/python3 -c ' +import json, sys +name = sys.argv[1] +for devices in json.load(sys.stdin)["devices"].values(): + for d in devices: + if d["name"] == name: + print(d["udid"]); sys.exit(0) +sys.exit(1)' "$1" +} + +BOOTED_BY_US=() +cleanup() { + for udid in ${BOOTED_BY_US[@]+"${BOOTED_BY_US[@]}"}; do + xcrun simctl status_bar "$udid" clear >/dev/null 2>&1 || true + xcrun simctl shutdown "$udid" >/dev/null 2>&1 || true + done +} +trap cleanup EXIT + +mkdir -p "$FASTLANE_DIR" + +for profile in $PROFILES; do + case "$profile" in + iphone) sim="$IPHONE_SIM"; materials_dir="materials/screenshots/ios/6-5-inch" ;; + ipad) sim="$IPAD_SIM"; materials_dir="materials/screenshots/ipados/13-inch" ;; + *) echo "Unbekanntes Profil '$profile' (erlaubt: iphone ipad)" >&2; exit 1 ;; + esac + + udid="$(sim_udid "$sim")" || { + echo "Simulator '$sim' fehlt, lege ihn an…" + udid="$(xcrun simctl create "$sim" "$sim")" + } + + echo "" + echo "=== Profil '$profile' ($sim, $udid) -> $materials_dir ===" + mkdir -p "$materials_dir" + rm -f "$materials_dir"/*.png "$FASTLANE_DIR/${profile}_"*.jpg + + if ! xcrun simctl list devices | grep -q "$udid) (Booted)"; then + xcrun simctl boot "$udid" + BOOTED_BY_US+=("$udid") + fi + xcrun simctl bootstatus "$udid" -b >/dev/null + + # Apple-typische Statusleiste: 9:41, volles WLAN/Netz, Akku voll. + xcrun simctl status_bar "$udid" override \ + --time "9:41" --dataNetwork wifi --wifiMode active --wifiBars 3 \ + --cellularMode active --cellularBars 4 --operatorName "" \ + --batteryState charged --batteryLevel 100 + + # Frische, ausgeloggte Sitzung: App löschen und Schlüsselbund leeren + # (flutter_secure_storage überlebt auf iOS eine Deinstallation). + xcrun simctl uninstall "$udid" "$BUNDLE_ID" >/dev/null 2>&1 || true + xcrun simctl keychain "$udid" reset >/dev/null 2>&1 || true + + SCREENSHOT_OUT_DIR="$materials_dir" flutter drive \ + -d "$udid" \ + --driver=test_driver/integration_test.dart \ + --target=integration_test/screenshot_test.dart \ + --"$BUILD_MODE" \ + --no-dds + + # Kanonische Bilder mit Profil-Präfix in den Fastlane-Upload-Ordner spiegeln. + # Als JPEG, weil App Store Connect Screenshots mit Alpha-Kanal ablehnt und + # takeScreenshot RGBA-PNGs liefert. + for f in "$materials_dir"/*.png; do + sips -s format jpeg -s formatOptions best "$f" \ + --out "$FASTLANE_DIR/${profile}_$(basename "${f%.png}").jpg" >/dev/null + done + echo "Profil '$profile' fertig: $(ls -1 "$materials_dir"/*.png 2>/dev/null | wc -l | tr -d ' ') Screenshots" +done + +echo "" +echo "Kanonische Screenshots: materials/screenshots/{ios/6-5-inch,ipados/13-inch}/" +echo "Upload: cd ios && fastlane upload_screenshots"