diff --git a/CHANGELOG.md b/CHANGELOG.md index 81384af..b5ebe08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.9.1] - 2026-11-03 (versionCode 72) + +Ersetzt 1.9.0 (versionCode 71), deren Play-Store-Veröffentlichung wegen des folgenden Absturzes abgebrochen wurde. Enthält alle Änderungen aus 1.9.0. + +### Fixed + +- Absturz beim App-Start auf einzelnen Android-Geräten (`connectAnimatedNodes: Animated node with tag (parent) does not exist`, gemeldet für v1.8.0 auf Android 12): Der Splash-Screen erzeugte bei jedem Re-Render neue native Animations-Knoten (#381, #382) + ## [1.9.0] - 2026-11-03 (versionCode 71) Änderungen seit der Play-Store-Version 1.8.0 (versionCode 69). diff --git a/CLAUDE.md b/CLAUDE.md index f47e403..6ad12d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ **Merke und Male** — Gedächtnistraining-App für Kinder (React Native / Expo). Spieler sehen ein Bild kurz, zeichnen es aus dem Gedächtnis, vergleichen das Ergebnis. -- **Aktuell: v1.9.0** (package.json + app.json; versionCode 71) — **Play Store: v1.8.0 / versionCode 69**; v1.9.0 ist Release-Kandidat auf `testing` (versionCode 70 wurde nie im Store veröffentlicht) +- **Aktuell: v1.9.1** (package.json + app.json; versionCode 72) — **Play Store: v1.8.0 / versionCode 69**; v1.9.1 ist Release-Kandidat (versionCodes 70 und 71/v1.9.0 wurden nie im Store veröffentlicht) - **Mindestanforderung Android: API 26 (Android 8.0 Oreo)** — Nexus 6 (max. API 25) wird nicht mehr unterstützt (Issue #172, geschlossen) - **Live Demo:** https://s540d.github.io/DrawFromMemory/ - **Repo:** https://github.com/S540d/DrawFromMemory @@ -244,7 +244,7 @@ Niemals `rotation`/`origin`-Props an SVG-Elemente geben, die auch auf Web gerend ## Wachstums-Roadmap (Issue #219) -Übergeordneter Plan für nachhaltiges Wachstum im Play Store. Stand: Play Store @ v1.8.0 / versionCode 69, `main` @ v1.9.0 / versionCode 70 (nie veröffentlicht), `testing` @ v1.9.0 / versionCode 71 = Release-Kandidat (Release-Aufgabe Issue #267). `testing` enthält gegenüber `main` auch User-Facing-Änderungen (neues Icon/Farben, Pause-Dialog, Einstellungs-Gruppierung, Impressum, Startseiten-Redesign) — Details im `CHANGELOG.md` unter [1.9.0]. Themen-Packs sind über `LevelImage.pack?: string` getaggt; neue Packs brauchen Render-Dateien in `components/levelImages/` + Registry-/Pool-Einträge, visueller Stil verbindlich laut [`docs/ILLUSTRATION_STYLEGUIDE.md`](docs/ILLUSTRATION_STYLEGUIDE.md). +Übergeordneter Plan für nachhaltiges Wachstum im Play Store. Stand: Play Store @ v1.8.0 / versionCode 69, `main` = `testing` @ v1.9.1 / versionCode 72 = Release-Kandidat (v1.9.0/vc71 wegen Splash-Crash #381 nicht veröffentlicht). 1.9.x enthält gegenüber 1.8.0 User-Facing-Änderungen (neues Icon/Farben, Pause-Dialog, Einstellungs-Gruppierung, Impressum, Startseiten-Redesign) — Details im `CHANGELOG.md` unter [1.9.0]. Themen-Packs sind über `LevelImage.pack?: string` getaggt; neue Packs brauchen Render-Dateien in `components/levelImages/` + Registry-/Pool-Einträge, visueller Stil verbindlich laut [`docs/ILLUSTRATION_STYLEGUIDE.md`](docs/ILLUSTRATION_STYLEGUIDE.md). Vollständige P0/P1/P2-Statustabellen und Themen-Pack-Architektur-Details: [`docs/ROADMAP.md`](docs/ROADMAP.md). diff --git a/app.json b/app.json index 08ba95d..a4967cb 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Merke und Male", "slug": "merke-und-male", - "version": "1.9.0", + "version": "1.9.1", "platforms": ["ios", "android", "web"], "orientation": "default", "icon": "./assets/icons/app-icon.png", @@ -32,7 +32,7 @@ }, "package": "com.s540d.merkeundmale", "permissions": [], - "versionCode": 71, + "versionCode": 72, "minSdkVersion": 26, "newArchEnabled": false, "playStoreUrl": "https://play.google.com/store/apps/details?id=com.s540d.merkeundmale", diff --git a/components/AnimatedSplashScreen.tsx b/components/AnimatedSplashScreen.tsx index 9974d52..31e75e4 100644 --- a/components/AnimatedSplashScreen.tsx +++ b/components/AnimatedSplashScreen.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { memo, useEffect, useMemo, useRef } from 'react'; import { View, StyleSheet, Animated, Dimensions } from 'react-native'; import Svg, { Path, Rect } from 'react-native-svg'; import Colors from '../constants/Colors'; @@ -13,7 +13,7 @@ interface Props { * Animierter Splash Screen mit Fade-In und Scale-Animationen. * Zeigt App-Titel und Untertitel, dann Übergang zum Home Screen. */ -export default function AnimatedSplashScreen({ onFinish }: Props) { +function AnimatedSplashScreen({ onFinish }: Props) { const { t } = useTranslation(); const titleOpacity = useRef(new Animated.Value(0)).current; const titleScale = useRef(new Animated.Value(0.7)).current; @@ -135,10 +135,26 @@ export default function AnimatedSplashScreen({ onFinish }: Props) { onFinishRef, ]); - const spin = iconRotation.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '360deg'], - }); + // Interpolation und Transform-Arrays nur einmal erzeugen: ein neuer Animated-Node pro + // Render (z. B. ausgelöst durch Sprach-/Font-Init im Root-Layout) wird beim nativen + // Treiber verworfen, während connectAnimatedNodes noch in der Queue steht → Crash + // "Animated node with tag (parent) does not exist" (Fabric, v1.8.0 Play-Store-Report). + const iconTransform = useMemo( + () => [ + { scale: iconScale }, + { + rotate: iconRotation.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }), + }, + ], + [iconScale, iconRotation], + ); + const pencilTransform = useMemo( + () => [...pencilTranslate.getTranslateTransform(), { rotate: '32deg' }], + [pencilTranslate], + ); return ( @@ -152,7 +168,7 @@ export default function AnimatedSplashScreen({ onFinish }: Props) { style={[ styles.iconContainer, { - transform: [{ scale: iconScale }, { rotate: spin }], + transform: iconTransform, }, ]} > @@ -179,7 +195,7 @@ export default function AnimatedSplashScreen({ onFinish }: Props) { styles.pencil, { opacity: pencilOpacity, - transform: [...pencilTranslate.getTranslateTransform(), { rotate: '32deg' }], + transform: pencilTransform, }, ]} > @@ -221,6 +237,9 @@ export default function AnimatedSplashScreen({ onFinish }: Props) { ); } +// memo: Re-Renders des Root-Layouts während des Splash erreichen die Animated-Nodes nicht. +export default memo(AnimatedSplashScreen); + const { width, height } = Dimensions.get('window'); const styles = StyleSheet.create({ diff --git a/components/__tests__/AnimatedSplashScreen.test.tsx b/components/__tests__/AnimatedSplashScreen.test.tsx index bfe5d38..5c4975f 100644 --- a/components/__tests__/AnimatedSplashScreen.test.tsx +++ b/components/__tests__/AnimatedSplashScreen.test.tsx @@ -84,4 +84,20 @@ describe('AnimatedSplashScreen', () => { }); }).not.toThrow(); }); + + it('should not create new animated interpolation nodes on rerender', () => { + // Regression: neue Animated-Nodes pro Render crashten den nativen Treiber + // (connectAnimatedNodes: parent does not exist, Play-Store-Report v1.8.0). + const { Animated } = require('react-native'); + const interpolateSpy = jest.spyOn(Animated.Value.prototype, 'interpolate'); + const { rerender } = render(); + const callsAfterMount = interpolateSpy.mock.calls.length; + + rerender(); + rerender(); + + expect(callsAfterMount).toBe(1); + expect(interpolateSpy).toHaveBeenCalledTimes(callsAfterMount); + interpolateSpy.mockRestore(); + }); }); diff --git a/package-lock.json b/package-lock.json index 8b155ba..15ed014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "merke-und-male", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "merke-und-male", - "version": "1.9.0", + "version": "1.9.1", "hasInstallScript": true, "dependencies": { "@expo-google-fonts/nunito": "^0.4.2", diff --git a/package.json b/package.json index 83c4ea1..798c41f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "merke-und-male", - "version": "1.9.0", + "version": "1.9.1", "main": "expo-router/entry", "homepage": "https://s540d.github.io/DrawFromMemory/", "repository": {