Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- „Von Level 1 neu starten" setzt den Galerie-Speicherstatus zurück (vorher blieb „In Galerie speichern" ggf. gesperrt)
- Ergebnis-Feedback wechselte bei jedem Re-Render, weil die rotierende Nachricht im Render gewählt wurde — jetzt einmal pro Bewertung; DE/EN-Texte für 1–2 Sterne laufen nicht mehr auseinander

### Changed

- Startseite entschlackt: „Spiel starten" ist die einzige auffällige Aktion (ohne Dauer-Puls); Tägliche Challenge als dezenter Textlink darunter (ohne ⚡, Farbrahmen, Countdown); Level/Galerie/Freies Malen als einfarbige Textlinks im Footer; Statistik als eine Textzeile statt drei Emoji-Karten; Streak-Badge und schwebende Sterne entfernt — die Serie steht nur noch in Malis Begrüßung
Expand Down
10 changes: 10 additions & 0 deletions components/__tests__/GamePhaseComponents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@ describe('ResultPhase', () => {
expect(texts).toContain('feedback-en-5');
});

it('selects feedback once per rating, not on every re-render', () => {
const { getRatingFeedback } = require('../../services/RatingManager');
getRatingFeedback.mockClear();
const { rerender } = render(<ResultPhase {...resultProps} userRating={4} />);
rerender(
<ResultPhase {...resultProps} userRating={4} screenWidth={resultProps.screenWidth + 1} />,
);
expect(getRatingFeedback).toHaveBeenCalledTimes(1);
});

it('shows saved label when savedToGallery is true', () => {
const { UNSAFE_getAllByType } = render(<ResultPhase {...resultProps} savedToGallery={true} />);
const texts = getAllTexts(UNSAFE_getAllByType);
Expand Down
22 changes: 13 additions & 9 deletions components/game/ResultPhase.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal } from 'react-native';
import DrawingCanvas from '@components/DrawingCanvas';
import LevelImageDisplay from '@components/LevelImageDisplay';
Expand Down Expand Up @@ -50,13 +50,17 @@ export default function ResultPhase({
}
}, [isLastLevel, userRating]);

const getFeedbackText = (rating: number) => {
if (rating >= 1 && rating <= 5) {
const feedback = getRatingFeedback(rating as StarRating);
return currentLang === 'en' ? feedback.messageEn : feedback.message;
}
return t('game.result.tapStars');
};
// Feedback einmal pro Bewertung auswählen: getRatingFeedback() rotiert durch
// Nachrichten, ein Aufruf im Render ließ den Text bei jedem Re-Render wechseln.
const feedback = useMemo(
() => (userRating >= 1 && userRating <= 5 ? getRatingFeedback(userRating as StarRating) : null),
[userRating],
);
const feedbackText = feedback
? currentLang === 'en'
? feedback.messageEn
: feedback.message
: t('game.result.tapStars');

const imageSize = Math.min(Math.max(screenWidth * 0.44, 140), 220);

Expand Down Expand Up @@ -230,7 +234,7 @@ export default function ResultPhase({
)}
</View>
<Text style={[styles.feedbackText, { color: colors.text.secondary }]}>
{getFeedbackText(userRating)}
{feedbackText}
</Text>
</View>
</AnimatedFeedback>
Expand Down
17 changes: 10 additions & 7 deletions services/RatingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ const lowRatingMessages = {

let lowRatingMessageIndex = 0;

function getRotatingLowRatingMessage(language: 'de' | 'en' = 'de'): string {
const messages = lowRatingMessages[language];
const message = messages[lowRatingMessageIndex % messages.length];
// DE und EN als Paar mit demselben Index (vorher zwei getrennte Aufrufe,
// die den Zähler doppelt weiterschoben und die Sprachen auseinanderlaufen ließen).
function getRotatingLowRatingMessages(): { de: string; en: string } {
const idx = lowRatingMessageIndex % lowRatingMessages.de.length;
lowRatingMessageIndex++;
return message;
return { de: lowRatingMessages.de[idx], en: lowRatingMessages.en[idx] };
}

/**
Expand Down Expand Up @@ -91,14 +92,16 @@ function getRotatingHighRatingMessages(stars: 4 | 5): { de: string; en: string }
export function getRatingFeedback(stars: StarRating, language: 'de' | 'en' = 'de'): RatingFeedback {
switch (stars) {
case 1:
case 2:
case 2: {
const low = getRotatingLowRatingMessages();
return {
stars,
message: getRotatingLowRatingMessage('de'),
messageEn: getRotatingLowRatingMessage('en'),
message: low.de,
messageEn: low.en,
animation: 'none',
sound: 'huchhu.mp3',
};
}

case 3:
return {
Expand Down
18 changes: 18 additions & 0 deletions services/__tests__/useGamePhase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,24 @@ describe('useGamePhase', () => {
expect(clearCanvas).toHaveBeenCalled();
});

it('resets savedToGallery when restarting from level 1', async () => {
const { result } = renderGamePhase({
initialLevel: 5,
drawingPaths: [{ points: [{ x: 0, y: 0 }], color: '#000', strokeWidth: 3 }],
});

await act(async () => {
await result.current.saveToGallery();
});
expect(result.current.savedToGallery).toBe(true);

act(() => {
result.current.restartFromLevel1();
});

expect(result.current.savedToGallery).toBe(false);
});

// --- Replay ---

it('starts replay', () => {
Expand Down
1 change: 1 addition & 0 deletions services/useGamePhase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export function useGamePhase({
const restartFromLevel1 = () => {
setPhase('memorize');
setUserRating(0);
setSavedToGallery(false);
clearCanvas();
setLevelNumber(1); // triggers level-init useEffect
};
Expand Down
Loading