chahinebrini 5d6c322129 wip: KeyboardAwareSheet migrations + Snake/Tetris UI + iron.png + useMe live-update
Sheets via neuer KeyboardAwareSheet-Composable (in Modal pattern, auto-grow
mit Tastatur, paddingBottom-Lift): EditMail, AddDomain, CreateRoom, ConnectMail.
GameOverScreen behält Spring-Slide-In, nutzt RN Keyboard.addListener für Lift.

- KeyboardAwareSheet.tsx — universal modal with sheet-grow + keyboard-padding
- react-native-keyboard-controller installiert + KeyboardProvider in Root
- Snake: time + ScoreProgressBar + useSnakeSounds (haptic, audio TODO)
- Tetris: title weg, Buttons zentriert, kein Pressable mit style-fn
- DPad-Buttons 60→48, more bg, no scale
- useMe: pub-sub listener pattern für app-weite avatar/nickname-Updates
- dm.tsx: resolveAvatar wrap (iron.png-Warning)
- Mail-error-humanizer + locales

Recovery-Doc-Update in docs/internal/RECOVERY_LOG_2026-05-10.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:59:25 +02:00

79 lines
3.0 KiB
TypeScript

import { useEffect, useRef } from 'react';
import * as Haptics from 'expo-haptics';
/**
* Snake-Sound + Haptic-Helper.
*
* Aktuell: nur Haptics (Apple Taptic-Engine, Android-Vibrator-Falls-Available).
* Funktioniert SOFORT ohne weitere Setup-Schritte.
*
* UPGRADE-PFAD zu echtem 8-Bit-Retro-Sound:
*
* 1. 4 kurze Audio-Files in `apps/rebreak-native/assets/sounds/` ablegen:
* - `snake-eat.mp3` — Apple-Pickup, ~80ms, tonale "blip"-Töne (chiptune)
* - `snake-move.mp3` — Optional, Tick-Sound bei jeder Bewegung, ~30ms, dezent
* - `snake-gameover.mp3` — Death, ~400ms, abfallende Töne
* - `snake-record.mp3` — New-Record, ~600ms, aufsteigender Chime
*
* Free-Quellen (CC0): freesound.org, opengameart.org/content/8-bit-sound-pack,
* sfxr.me (in-browser-Generator für klassische 8-Bit-Sounds).
*
* 2. `expo-av` (oder `expo-audio` nach SDK-54-Migration) installieren falls nicht da:
* `pnpm add expo-av` (im rebreak-native-Workspace)
*
* 3. In dieser Datei oben einfügen:
* ```ts
* import { Audio } from 'expo-av';
* const eatSrc = require('../assets/sounds/snake-eat.mp3');
* const moveSrc = require('../assets/sounds/snake-move.mp3');
* const gameoverSrc = require('../assets/sounds/snake-gameover.mp3');
* const recordSrc = require('../assets/sounds/snake-record.mp3');
* ```
*
* 4. Im Hook-useEffect die Sounds preloaden:
* ```ts
* Audio.Sound.createAsync(eatSrc, { volume: 0.5 }).then((r) => (eatRef.current = r.sound));
* // … analog für alle drei
* ```
*
* 5. In den `play*`-Funktionen `await ref.current?.replayAsync()` aufrufen.
*
* Wenn die Files fehlen aber expo-av da ist: keine Crashes — die createAsync-Calls
* fangen den Error und der Hook läuft im Haptic-only-Mode weiter.
*/
export function useSnakeSounds(enabled: boolean = true) {
const enabledRef = useRef(enabled);
useEffect(() => {
enabledRef.current = enabled;
}, [enabled]);
useEffect(() => {
return () => {
// Cleanup: bei späterer Audio-Integration unloadAsync() für alle Sounds.
};
}, []);
return {
playEat: () => {
if (!enabledRef.current) return;
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {});
// TODO Audio: eatRef.current?.replayAsync().catch(() => {});
},
playMove: () => {
// Bewusst leer — sonst zu viel Vibration bei jedem Tick.
// Nur via Audio (subtiler als Haptic).
// TODO Audio: moveRef.current?.replayAsync().catch(() => {});
},
playGameOver: () => {
if (!enabledRef.current) return;
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});
// TODO Audio: gameoverRef.current?.replayAsync().catch(() => {});
},
playNewRecord: () => {
if (!enabledRef.current) return;
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
// TODO Audio: recordRef.current?.replayAsync().catch(() => {});
},
};
}