import AsyncStorage from '@react-native-async-stor...

Created on: December 8, 2025

Answered using GPT-5.1 Thinking by Chat01

Question

import AsyncStorage from '@react-native-async-storage/async-storage';
import { CameraView, useCameraPermissions } from 'expo-camera';
import { LinearGradient } from 'expo-linear-gradient';
import * as LocalAuthentication from 'expo-local-authentication';
import * as Speech from 'expo-speech';
import * as SQLite from 'expo-sqlite';
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Alert,
Animated,
Easing,
SafeAreaView,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
useWindowDimensions,
View,
} from 'react-native';

// -----------------------
// Тема приложения (контекст)
// -----------------------
const ThemeContext = createContext({
primaryColor: '#ff7a59',
levelColor: '#ffe66d',
getScreenColors: () => ['#0f2027', '#203a43', '#2c5364'],
});

// -----------------------
// SQLite DB (қолжетімді болса ғана)
// -----------------------
let db = null;

try {
if (SQLite && typeof SQLite.openDatabase === 'function') {
db = SQLite.openDatabase('ai_kids.db');
} else {
console.warn('SQLite.openDatabase табылмады. Тек AsyncStorage қолданамыз.');
}
} catch (e) {
console.warn('SQLite ашу кезінде қате. Тек AsyncStorage қолданамыз.', e);
}

// -----------------------
// Мультфильм кейіпкерлері
// -----------------------
const CHARACTERS = [
{ id: 'robot', emoji: '🤖', label: 'Робо-Дос', color: '#ff9ff3' },
{ id: 'bunny', emoji: '🐰', label: 'Қуаныш Қоян', color: '#feca57' },
{ id: 'lion', emoji: '🦁', label: 'Батыр Арыстан', color: '#ff6b6b' },
];

// Авторизация экранындағы слайдер
const WELCOME_SLIDES = [
{
key: 'slide1',
title: 'Ойнап үйренеміз',
text: 'Робо-Дос сені математика, әріптер мен түстерді ойнап үйретеді.',
emoji: '🤖',
background: ['#ff9a9e', '#fad0c4'],
},
{
key: 'slide2',
title: 'Дауыспен оқу',
text: 'Қазақша мәтіндерді бірге дауыстап оқимыз.',
emoji: '📖',
background: ['#a18cd1', '#fbc2eb'],
},
{
key: 'slide3',
title: 'Сиқырлы камера',
text: 'Камера арқылы заттарды болжайтын AI-досың бар.',
emoji: '📷',
background: ['#43e97b', '#38f9d7'],
},
];

const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);

// -----------------------
// Темы для персонажей
// -----------------------
const THEMES = {
robot: {
primaryColor: '#ff7a59',
levelColor: '#ffe66d',
screenGradients: {
default: ['#0f2027', '#203a43', '#2c5364'],
auth: ['#141e30', '#243b55'],
home: ['#0f2027', '#203a43', '#2c5364'],
reading: ['#ff9966', '#ff5e62'],
games: ['#4e54c8', '#8f94fb'],
quiz: ['#30cfd0', '#330867'],
progress: ['#f6d365', '#fda085'],
image: ['#f093fb', '#f5576c'],
animals: ['#84fab0', '#8fd3f4'],
alphabet: ['#89f7fe', '#66a6ff'],
colors: ['#f9d976', '#f39f86'],
missions: ['#5ee7df', '#b490ca'],
achievements: ['#fbc2eb', '#a6c1ee'],
settings: ['#cfd9df', '#e2ebf0'],
mood: ['#ffecd2', '#fcb69f'],
calm: ['#2193b0', '#6dd5ed'],
story: ['#41295a', '#2F0743'],
},
},
bunny: {
primaryColor: '#ffb74d',
levelColor: '#ffd54f',
screenGradients: {
default: ['#ff9a9e', '#fecfef'],
auth: ['#ffdde1', '#ee9ca7'],
home: ['#ff9a9e', '#fecfef'],
reading: ['#ffecd2', '#fcb69f'],
games: ['#a18cd1', '#fbc2eb'],
quiz: ['#fbc2eb', '#a6c1ee'],
progress: ['#f6d365', '#fda085'],
image: ['#f093fb', '#f5576c'],
animals: ['#84fab0', '#8fd3f4'],
alphabet: ['#89f7fe', '#66a6ff'],
colors: ['#f9d976', '#f39f86'],
missions: ['#5ee7df', '#b490ca'],
achievements: ['#fbc2eb', '#a6c1ee'],
settings: ['#cfd9df', '#e2ebf0'],
mood: ['#ffecd2', '#fcb69f'],
calm: ['#a1c4fd', '#c2e9fb'],
story: ['#fbc2eb', '#a18cd1'],
},
},
lion: {
primaryColor: '#ff7043',
levelColor: '#ffb300',
screenGradients: {
default: ['#f2994a', '#f2c94c'],
auth: ['#f46b45', '#eea849'],
home: ['#f2994a', '#f2c94c'],
reading: ['#f7971e', '#ffd200'],
games: ['#f46b45', '#f093fb'],
quiz: ['#f6d365', '#fda085'],
progress: ['#fbc2eb', '#a6c1ee'],
image: ['#f093fb', '#f5576c'],
animals: ['#84fab0', '#8fd3f4'],
alphabet: ['#89f7fe', '#66a6ff'],
colors: ['#f9d976', '#f39f86'],
missions: ['#5ee7df', '#b490ca'],
achievements: ['#fbc2eb', '#a6c1ee'],
settings: ['#cfd9df', '#e2ebf0'],
mood: ['#ffecd2', '#fcb69f'],
calm: ['#f79d00', '#64f38c'],
story: ['#41295a', '#2F0743'],
},
},
};

// -----------------------
// Тұрақты деректер
// -----------------------
const QUIZ_DATA = [
{
text: '1) AI дегеніміз не?',
options: [
'А) Автоматты интернет',
'Ә) Жасанды интеллект',
'Б) Жылдам интернет',
'В) Ойын түрі',
],
correctIndex: 1,
},
{
text: '2) Камера арқылы не істейміз?',
options: [
'А) Суретті танимыз',
'Ә) Музыка тыңдаймыз',
'Б) Интернет қосамыз',
'В) Ойын өшіреміз',
],
correctIndex: 0,
},
{
text: '3) Дауыспен оқу не үшін керек?',
options: [
'А) Баланың мәтін оқуына көмектесу',
'Ә) Телефонды зарядтау',
'Б) Камераны қосу',
'В) Тек ересектерге',
],
correctIndex: 0,
},
];

const READING_TEXT =
'Бүгін біз ойын арқылы үйренеміз! Жасанды интеллект бізге дауыспен оқып, суреттерді тануға көмектеседі.';

// Жануарлар ойыны
const ANIMAL_QUIZ = [
{ id: 'cat', name: 'Мысық', emoji: '🐱' },
{ id: 'dog', name: 'Ит', emoji: '🐶' },
{ id: 'cow', name: 'Сиыр', emoji: '🐮' },
{ id: 'horse', name: 'Жылқы', emoji: '🐴' },
{ id: 'chicken', name: 'Тауық', emoji: '🐔' },
{ id: 'lion', name: 'Арыстан', emoji: '🦁' },
];

// Қазақ әліпбиі
const KAZAKH_ALPHABET = [
{ letter: 'А', word: 'алма' },
{ letter: 'Ә', word: 'әже' },
{ letter: 'Б', word: 'бал' },
{ letter: 'В', word: 'вагон' },
{ letter: 'Г', word: 'гүл' },
{ letter: 'Ғ', word: 'ғалам' },
{ letter: 'Д', word: 'доп' },
{ letter: 'Е', word: 'ер' },
{ letter: 'Ж', word: 'жұлдыз' },
{ letter: 'З', word: 'зебра' },
{ letter: 'И', word: 'игі' },
{ letter: 'Й', word: 'йогурт' },
{ letter: 'К', word: 'кітап' },
{ letter: 'Қ', word: 'қоян' },
{ letter: 'Л', word: 'лемур' },
{ letter: 'М', word: 'мысық' },
{ letter: 'Н', word: 'нұр' },
{ letter: 'Ң', word: 'ңғой (мысал)' },
{ letter: 'О', word: 'ойыншық' },
{ letter: 'Ө', word: 'өрік' },
{ letter: 'П', word: 'піл' },
{ letter: 'Р', word: 'раушан' },
{ letter: 'С', word: 'сағат' },
{ letter: 'Т', word: 'түйе' },
{ letter: 'У', word: 'уылдырық' },
{ letter: 'Ұ', word: 'ұшақ' },
{ letter: 'Ү', word: 'үйрек' },
{ letter: 'Ф', word: 'фигура' },
{ letter: 'Х', word: 'хат' },
{ letter: 'Һ', word: 'һә' },
{ letter: 'Ц', word: 'цирк' },
{ letter: 'Ч', word: 'чай' },
{ letter: 'Ш', word: 'шар' },
{ letter: 'Щ', word: 'щетка' },
{ letter: 'Ы', word: 'ырыс' },
{ letter: 'І', word: 'інді' },
{ letter: 'Э', word: 'экран' },
{ letter: 'Ю', word: 'юла' },
{ letter: 'Я', word: 'якорь' },
];

// Түстер
const COLOR_LESSONS = [
{ id: 'red', name: 'Қызыл', text: 'Қызыл – алма түсі', bg: '#ff6b6b' },
{ id: 'blue', name: 'Көк', text: 'Көк – аспан түсі', bg: '#4dabf7' },
{ id: 'green', name: 'Жасыл', text: 'Жасыл – шөптің түсі', bg: '#51cf66' },
{ id: 'yellow', name: 'Сары', text: 'Сары – күннің түсі', bg: '#ffd43b' },
];

// Жетістіктер
const ACHIEVEMENTS = [
{
id: 'firstStar',
type: 'stars',
threshold: 1,
title: 'Алғашқы жұлдыз',
description: '1 жұлдыз жинадың.',
},
{
id: 'tenStars',
type: 'stars',
threshold: 10,
title: 'Жарқыраған жұлдыздар',
description: '10 жұлдыз жинадың.',
},
{
id: 'games5',
type: 'gamesPlayed',
threshold: 5,
title: 'Ойын шебері',
description: '5 ойын ойнадың.',
},
{
id: 'tests3',
type: 'testsPassed',
threshold: 3,
title: 'Білімгер',
description: '3 тесттен сәтті өттің.',
},
{
id: 'stars25',
type: 'stars',
threshold: 25,
title: 'Супер жұлдыз',
description: '25 жұлдыз жинадың. Нағыз жұлдызсың!',
},
];

// Көңіл-күй опциялары
const MOOD_OPTIONS = [
{ id: 'happy', label: 'Көңілдімін', emoji: '😊', color: '#ffe082' },
{ id: 'ok', label: 'Жақсы', emoji: '🙂', color: '#a5d6a7' },
{ id: 'tired', label: 'Шаршадым', emoji: '😴', color: '#ce93d8' },
{ id: 'sad', label: 'Көңілсіз', emoji: '😕', color: '#90caf9' },
];

// Таңдау хикаясы
const STORY_NODES = {
start: {
id: 'start',
title: '1-көрініс: Таңғы таңдау',
text: 'Бүгін Робо-Дос сені оятты. Не істейсің?',
choices: [
{ id: 'fast', text: 'Тез жуынуға барамын', nextId: 'school', starsDelta: 1 },
{ id: 'sleep', text: 'Тағы 5 минут ұйықтаймын', nextId: 'late' },
],
},
school: {
id: 'school',
title: '2-көрініс: Мектепке жол',
text: 'Жолда досыңды көрдің. Ол сенімен бірге барғысы келеді.',
choices: [
{ id: 'friend', text: 'Бірге барып, әңгімелесеміз', nextId: 'classFun', starsDelta: 1 },
{ id: 'rush', text: 'Мен асығып тұрмын, кейін сөйлесеміз', nextId: 'classSolo' },
],
},
late: {
id: 'late',
title: '2-көрініс: Кешігу',
text: 'Сабаққа кешіктің. Мұғалім сені күтіп тұр.',
choices: [
{
id: 'apologize',
text: 'Кешіккенім үшін кешірім сұраймын',
nextId: 'classSolo',
starsDelta: 1,
},
{ id: 'silent', text: 'Ештеңе демеймін', nextId: 'endingOk' },
],
},
classFun: {
id: 'classFun',
title: '3-көрініс: Бірге оқу',
text: 'Сен досыңмен бірге отырып, есептер шығарасың. Екеуің де бәрін түсіндіңдер!',
choices: [
{ id: 'endGood', text: 'Күнім өте керемет өтті! 🎉', nextId: 'endingGood', starsDelta: 1 },
],
},
classSolo: {
id: 'classSolo',
title: '3-көрініс: Жауапкершілік',
text: 'Сен мұғалімді тыңдап, барлық тапсырманы мұқият орындайсың.',
choices: [
{
id: 'endCalm',
text: 'Бүгін жақсы еңбек еттім деп ойлаймын 😊',
nextId: 'endingGood',
starsDelta: 1,
},
],
},
endingGood: {
id: 'endingGood',
title: 'Қорытынды: Керемет күн',
text: 'Сен дұрыс таңдаулар жасап, пайдалы күн өткіздің. Робо-Дос сенімен мақтанады!',
choices: [],
isEnd: true,
},
endingOk: {
id: 'endingOk',
title: 'Қорытынды: Ойланатын күн',
text: 'Бүгін бәрі онша болмады, бірақ келесі жолы жақсырақ істеуге болады!',
choices: [],
isEnd: true,
},
};

// -----------------------
// Көмекші компоненттер
// -----------------------
const PrimaryButton = ({ title, onPress }) => {
const { primaryColor } = useContext(ThemeContext);
const scale = useRef(new Animated.Value(1)).current;

const handlePressIn = () => {
Animated.spring(scale, {
toValue: 0.93,
friction: 4,
tension: 80,
useNativeDriver: true,
}).start();
};

const handlePressOut = () => {
Animated.spring(scale, {
toValue: 1,
friction: 4,
tension: 80,
useNativeDriver: true,
}).start();
};

return (
<AnimatedTouchableOpacity
style={[
styles.primaryButton,
{
backgroundColor: primaryColor,
transform: [{ scale }],
},
]}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={0.9}
>
<Text style={styles.primaryButtonText}>{title}</Text>
</AnimatedTouchableOpacity>
);
};

const SecondaryButton = ({ title, onPress, light }) => {
const scale = useRef(new Animated.Value(1)).current;

const handlePressIn = () => {
Animated.spring(scale, {
toValue: 0.95,
friction: 4,
tension: 80,
useNativeDriver: true,
}).start();
};

const handlePressOut = () => {
Animated.spring(scale, {
toValue: 1,
friction: 4,
tension: 80,
useNativeDriver: true,
}).start();
};

return (
<AnimatedTouchableOpacity
style={[
styles.secondaryButton,
light && styles.secondaryButtonLight,
{ transform: [{ scale }] },
]}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={0.9}
>
<Text style={[styles.secondaryButtonText, light && styles.secondaryButtonTextDark]}>
{title}
</Text>
</AnimatedTouchableOpacity>
);
};

const Card = ({ children }) => {
const scale = useRef(new Animated.Value(0.96)).current;
const opacity = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.parallel([
Animated.timing(scale, {
toValue: 1,
duration: 300,
easing: Easing.out(Easing.quad),
useNativeDriver: true,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
]).start();
}, [opacity, scale]);

return (
<Animated.View style={[styles.card, { transform: [{ scale }], opacity }]}>
{children}
</Animated.View>
);
};

const Screen = ({ colors, screenKey, children }) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(24)).current;
const { getScreenColors } = useContext(ThemeContext);

const gradientColors = colors || getScreenColors(screenKey || 'default');

useEffect(() => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 450,
easing: Easing.out(Easing.quad),
useNativeDriver: true,
}),
Animated.timing(translateY, {
toValue: 0,
duration: 450,
easing: Easing.out(Easing.quad),
useNativeDriver: true,
}),
]).start();
}, [fadeAnim, translateY]);

return (
<LinearGradient colors={gradientColors} style={styles.gradient}>
<Animated.View
style={{
flex: 1,
opacity: fadeAnim,
transform: [{ translateY }],
}}
>
<ScrollView contentContainerStyle={styles.scroll}>{children}</ScrollView>
</Animated.View>
</LinearGradient>
);
};

const FadeInView = ({ children, style, duration = 600, delay = 0 }) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(16)).current;

useEffect(() => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration,
delay,
useNativeDriver: true,
}),
Animated.timing(translateY, {
toValue: 0,
duration,
delay,
easing: Easing.out(Easing.quad),
useNativeDriver: true,
}),
]).start();
}, [delay, duration, fadeAnim, translateY]);

return (
<Animated.View
style={[
style,
{
opacity: fadeAnim,
transform: [{ translateY }],
},
]}
>
{children}
</Animated.View>
);
};

const BouncyCharacters = ({ small }) => {
const bounce = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(bounce, {
toValue: -10,
duration: 600,
easing: Easing.out(Easing.quad),
useNativeDriver: true,
}),
Animated.timing(bounce, {
toValue: 0,
duration: 600,
easing: Easing.in(Easing.quad),
useNativeDriver: true,
}),
]),
).start();
}, [bounce]);

return (
<View style={styles.charactersRow}>
{CHARACTERS.map((ch) => (
<Animated.View
key={ch.id}
style={[
styles.characterBubble,
small && styles.characterBubbleSmall,
{
backgroundColor: ch.color,
transform: [{ translateY: bounce }],
},
]}
>
<Text style={styles.characterEmoji}>{ch.emoji}</Text>
<Text style={styles.characterLabel}>{ch.label}</Text>
</Animated.View>
))}
</View>
);
};

const WelcomeCarousel = () => {
const scrollX = useRef(new Animated.Value(0)).current;
const { width } = useWindowDimensions();

return (
<View style={styles.carouselContainer}>
<Animated.ScrollView
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
style={{ flexGrow: 0 }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: false },
)}
scrollEventThrottle={16}
>
{WELCOME_SLIDES.map((slide) => (
<View key={slide.key} style={{ width }}>
<LinearGradient colors={slide.background} style={styles.carouselCard}>
<Text style={styles.carouselEmoji}>{slide.emoji}</Text>
<Text style={styles.carouselTitle}>{slide.title}</Text>
<Text style={styles.carouselText}>{slide.text}</Text>
</LinearGradient>
</View>
))}
</Animated.ScrollView>

text
<View style={styles.carouselDots}> {WELCOME_SLIDES.map((_, index) => { const inputRange = [ (index - 1) * width, index * width, (index + 1) * width, ]; const dotWidth = scrollX.interpolate({ inputRange, outputRange: [8, 20, 8], extrapolate: 'clamp', }); return <Animated.View key={index} style={[styles.carouselDot, { width: dotWidth }]} />; })} </View> </View>

);
};

const LevelProgressBar = ({ stars }) => {
const { levelColor } = useContext(ThemeContext);
const progressAnim = useRef(new Animated.Value(0)).current;

useEffect(() => {
const starsInLevel = stars % 5;
const progress = starsInLevel / 5;
Animated.timing(progressAnim, {
toValue: progress,
duration: 500,
easing: Easing.out(Easing.quad),
useNativeDriver: false,
}).start();
}, [progressAnim, stars]);

const widthInterpolated = progressAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0%', '100%'],
});

return (
<View style={styles.levelBarOuter}>
<Animated.View
style={[
styles.levelBarInner,
{ width: widthInterpolated, backgroundColor: levelColor },
]}
/>
</View>
);
};

// Тыныс алу шеңбері
const BreathingCircle = ({ playing }) => {
const scale = useRef(new Animated.Value(1)).current;

useEffect(() => {
let animation;
if (playing) {
animation = Animated.loop(
Animated.sequence([
Animated.timing(scale, {
toValue: 1.25,
duration: 4000,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}),
Animated.timing(scale, {
toValue: 0.9,
duration: 4000,
easing: Easing.inOut(Easing.quad),
useNativeDriver: true,
}),
]),
);
animation.start();
} else {
scale.setValue(1);
}
return () => {
if (animation) {
animation.stop();
}
};
}, [playing, scale]);

return (
<View style={styles.breathingWrapper}>
<Animated.View style={[styles.breathingCircle, { transform: [{ scale }] }]} />
</View>
);
};

const speakKk = (text) => {
Speech.stop();
Speech.speak(text, {
language: 'kk-KZ',
rate: 0.95,
pitch: 1.0,
});
};

// -----------------------
// Негізгі қосымша
// -----------------------
export default function App() {
// --------- Auth / User ----------
const [authMode, setAuthMode] = useState('login'); // 'login' | 'register'
const [authStep, setAuthStep] = useState('credentials'); // 'credentials' | '2fa' | '3fa'
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [childName, setChildName] = useState('');
const [secretWord, setSecretWord] = useState('');
const [secretWordLogin, setSecretWordLogin] = useState('');
const [twoFaCode, setTwoFaCode] = useState('');
const [generatedCode, setGeneratedCode] = useState('');
const [savedUser, setSavedUser] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);

// --------- Навигация ----------
const [currentScreen, setCurrentScreen] = useState('home');

// --------- Прогресс ----------
const [progress, setProgress] = useState({
gamesPlayed: 0,
testsPassed: 0,
stars: 0,
});

// --------- Баптаулар ----------
const [settings, setSettings] = useState({
mathLevel: 'easy', // 'easy' | 'medium' | 'hard'
voiceHints: true,
chosenCharacterId: 'robot',
});

// --------- Күнделікті миссиялар ----------
const [missions, setMissions] = useState({
date: '',
items: [],
rewardCollected: false,
});

// --------- Математикалық ойын ----------
const [gameA, setGameA] = useState(0);
const [gameB, setGameB] = useState(0);
const [gameOp, setGameOp] = useState('+');
const [gameOptions, setGameOptions] = useState([]);
const [gameMessage, setGameMessage] = useState('');

// --------- Тест ----------
const [quizIndex, setQuizIndex] = useState(0);
const [quizCorrect, setQuizCorrect] = useState(0);
const [quizFinished, setQuizFinished] = useState(false);

// --------- Камера / AI ----------
const cameraRef = useRef(null);
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
const [cameraOpen, setCameraOpen] = useState(false);
const [aiResult, setAiResult] = useState('');
const [aiLoading, setAiLoading] = useState(false);

// --------- Жануарлар ойыны ----------
const [animalQuestion, setAnimalQuestion] = useState(ANIMAL_QUIZ[0]);
const [animalOptions, setAnimalOptions] = useState([]);
const [animalMessage, setAnimalMessage] = useState('');

// --------- Көңіл-күй ----------
const [moodToday, setMoodToday] = useState(null); // mood id
const [moodMessage, setMoodMessage] = useState('');

// --------- Тыныс алу ----------
const [breathingPlaying, setBreathingPlaying] = useState(false);
const [breathingLabel, setBreathingLabel] = useState('Дайынсың ба?');
const [breathingMessage, setBreathingMessage] = useState('');

// --------- Хикая ----------
const [storyNodeId, setStoryNodeId] = useState('start');
const [storyEnded, setStoryEnded] = useState(false);

// --------- Тема (персонажға байланысты) ----------
const currentThemeKey = settings.chosenCharacterId || 'robot';
const baseTheme = THEMES[currentThemeKey] || THEMES.robot;

const themeValue = {
primaryColor: baseTheme.primaryColor,
levelColor: baseTheme.levelColor,
getScreenColors: (screenKey) => {
const gradients = baseTheme.screenGradients || {};
if (gradients[screenKey]) return gradients[screenKey];
if (gradients.default) return gradients.default;
return ['#0f2027', '#203a43', '#2c5364'];
},
};

// --------- Көмекші функциялар ----------
const getTodayKey = () => {
const d = new Date();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return ${d.getFullYear()}-${mm}-${dd};
};

const createDefaultMissions = () => [
{ id: 'playGame', title: '1 математикалық ойын ойна', done: false },
{ id: 'doQuiz', title: '1 тесттен өт', done: false },
{ id: 'readText', title: 'Мәтінді дауыспен тыңда', done: false },
{ id: 'chooseMood', title: 'Көңіл-күйіңді таңда', done: false },
{ id: 'calmSession', title: '1 тыныс алу жаттығуын орында', done: false },
{ id: 'storyPlay', title: 'Таңдау хикаясын бір рет ойна', done: false },
];

const updateMissions = (updater) => {
setMissions((prev) => {
const nextState = typeof updater === 'function' ? updater(prev) : updater;
AsyncStorage.setItem('aiKidsMissions', JSON.stringify(nextState)).catch((e) => {
console.log('Save missions error', e);
});
return nextState;
});
};

const completeMission = (id) => {
updateMissions((prev) => {
if (!prev.items || prev.items.length === 0) return prev;
const items = prev.items.map((m) => (m.id === id ? { ...m, done: true } : m));
return { ...prev, items };
});
};

const updateSettings = (patch) => {
setSettings((prev) => {
const updated = { ...prev, ...patch };
AsyncStorage.setItem('aiKidsSettings', JSON.stringify(updated)).catch((e) =>
console.log('Save settings error', e),
);
return updated;
});
};

const speakIfEnabled = (text) => {
if (!settings.voiceHints) return;
speakKk(text);
};

// -----------------------
// Бастапқы жүктеу (SQLite + AsyncStorage)
// -----------------------
useEffect(() => {
if (db) {
db.transaction((tx) => {
tx.executeSql(
CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY NOT NULL, email TEXT, password TEXT, childName TEXT, secretWord TEXT );,
);

text
tx.executeSql( `CREATE TABLE IF NOT EXISTS progress ( id INTEGER PRIMARY KEY NOT NULL, gamesPlayed INTEGER, testsPassed INTEGER, stars INTEGER );`, ); }); } (async () => { try { if (db) { // USER db.transaction((tx) => { tx.executeSql( 'SELECT email, password, childName, secretWord FROM user LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const user = rows._array[0]; setSavedUser(user); await AsyncStorage.setItem('aiKidsUser', JSON.stringify(user)); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) { setSavedUser(JSON.parse(jsonUser)); } } }, (_, error) => { console.log('User select error', error); return false; }, ); }); // PROGRESS db.transaction((tx) => { tx.executeSql( 'SELECT gamesPlayed, testsPassed, stars FROM progress LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const p = rows._array[0]; const loaded = { gamesPlayed: p.gamesPlayed ?? 0, testsPassed: p.testsPassed ?? 0, stars: p.stars ?? 0, }; setProgress(loaded); await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(loaded)); } else { const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) { setProgress(JSON.parse(jsonProgress)); } } }, (_, error) => { console.log('Progress select error', error); return false; }, ); }); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) setSavedUser(JSON.parse(jsonUser)); const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) setProgress(JSON.parse(jsonProgress)); } // SETTINGS const jsonSettings = await AsyncStorage.getItem('aiKidsSettings'); if (jsonSettings) { setSettings(JSON.parse(jsonSettings)); } const today = getTodayKey(); // MISSIONS const jsonMissions = await AsyncStorage.getItem('aiKidsMissions'); if (jsonMissions) { try { const saved = JSON.parse(jsonMissions); if (saved.date === today) { setMissions(saved); } else { const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } catch (err) { console.log('Parse missions error', err); const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } else { const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } // MOOD const jsonMood = await AsyncStorage.getItem('aiKidsMood'); if (jsonMood) { try { const savedMood = JSON.parse(jsonMood); if (savedMood.date === today) { setMoodToday(savedMood.moodId); } } catch (e) { console.log('Parse mood error', e); } } } catch (e) { console.log('Load storage / SQL error', e); } })();

}, []);

// -----------------------
// Прогресс
// -----------------------
const addProgress = async ({ gamesDelta = 0, testsDelta = 0, starsDelta = 0 }) => {
try {
const updated = {
gamesPlayed: progress.gamesPlayed + gamesDelta,
testsPassed: progress.testsPassed + testsDelta,
stars: progress.stars + starsDelta,
};
setProgress(updated);
await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(updated));

text
if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM progress;'); tx.executeSql( 'INSERT INTO progress (gamesPlayed, testsPassed, stars) VALUES (?, ?, ?);', [updated.gamesPlayed, updated.testsPassed, updated.stars], () => console.log('Progress inserted into SQLite'), (_, error) => { console.log('Progress insert error', error); return false; }, ); }); } } catch (e) { console.log('Save progress error', e); }

};

const collectMissionReward = () => {
setMissions((prev) => {
if (!prev.items || prev.items.length === 0) {
Alert.alert('Миссиялар', 'Бүгінгі миссиялар табылмады.');
return prev;
}
const allDone = prev.items.every((m) => m.done);
if (!allDone) {
Alert.alert('Миссиялар', 'Алдымен барлық миссияны орындап шық.');
return prev;
}
if (prev.rewardCollected) {
Alert.alert('Миссиялар', 'Бүгінгі сыйлықты бұрын алғансың.');
return prev;
}
addProgress({ starsDelta: 5 });
speakIfEnabled('Керемет! Күнделікті миссиялар үшін қосымша 5 жұлдыз алдың!');
const updated = { ...prev, rewardCollected: true };
AsyncStorage.setItem('aiKidsMissions', JSON.stringify(updated)).catch((err) =>
console.log('Save missions reward error', err),
);
return updated;
});
};

// -----------------------
// Тыныс алу useEffect
// -----------------------
useEffect(() => {
let intervalId;
let timeoutId;
if (breathingPlaying) {
let phase = 0;
setBreathingLabel('Дем ал');
setBreathingMessage('');
intervalId = setInterval(() => {
phase = 1 - phase;
setBreathingLabel(phase === 0 ? 'Дем ал' : 'Дем шығар');
}, 4000);

text
timeoutId = setTimeout(() => { setBreathingPlaying(false); const msg = 'Керемет! Тыныс алу жаттығуын аяқтадың, 1 жұлдыз сыйлаймын.'; setBreathingMessage(msg); speakIfEnabled(msg); addProgress({ starsDelta: 1 }); completeMission('calmSession'); }, 24000); } else { setBreathingLabel('Дайынсың ба?'); } return () => { if (intervalId) clearInterval(intervalId); if (timeoutId) clearTimeout(timeoutId); };

}, [breathingPlaying]);

// -----------------------
// Auth логикасы
// -----------------------
const handleRegister = async () => {
if (!email || !password || !childName || !secretWord) {
Alert.alert('Қате', 'Барлық өрістерді толтырыңыз.');
return;
}
try {
const newUser = { email, password, childName, secretWord };
await AsyncStorage.setItem('aiKidsUser', JSON.stringify(newUser));
setSavedUser(newUser);

text
if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM user;'); tx.executeSql( 'INSERT INTO user (email, password, childName, secretWord) VALUES (?, ?, ?, ?);', [newUser.email, newUser.password, newUser.childName, newUser.secretWord], () => console.log('User inserted into SQLite'), (_, error) => { console.log('User insert error', error); return false; }, ); }); } Alert.alert('Тіркелу', 'Аккаунт сәтті жасалды! Енді кіруге болады.'); setAuthMode('login'); setAuthStep('credentials'); setPassword(''); setSecretWord(''); } catch (e) { Alert.alert('Қате', 'Пайдаланушыны сақтау кезінде қате кетті.'); }

};

const handleLoginCredentials = () => {
if (!savedUser) {
Alert.alert('Ақпарат', 'Алдымен тіркеліп алыңыз.');
return;
}
if (email === savedUser.email && password === savedUser.password) {
const code = Math.floor(100000 + Math.random() * 900000).toString();
setGeneratedCode(code);
setAuthStep('2fa');
setTwoFaCode('');
Alert.alert(
'2FA коды',
Демо режим: әдетте бұл код SMS немесе e-mail арқылы келеді.\nҚазір тек тест үшін код: ${code},
);
} else {
Alert.alert('Қате', 'E-mail немесе құпия сөз дұрыс емес.');
}
};

const handleCheck2FA = () => {
if (twoFaCode === generatedCode) {
setAuthStep('3fa');
} else {
Alert.alert('Қате', '2FA коды дұрыс емес.');
}
};

const finishLogin = () => {
setIsLoggedIn(true);
setCurrentScreen('home');
setAuthStep('credentials');
setSecretWordLogin('');
setTwoFaCode('');
};

const handleThreeFA = async () => {
try {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (hasHardware) {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'AI Kids App - 3FA тексеру',
fallbackLabel: 'Қауіпсіздік сөзімен растау',
});
if (result.success) {
finishLogin();
return;
}
}
if (savedUser && secretWordLogin === savedUser.secretWord) {
finishLogin();
} else {
Alert.alert('3FA', 'Қауіпсіздік сөзі дұрыс емес.');
}
} catch (e) {
Alert.alert('Қате', '3FA тексеру кезінде қате кетті.');
}
};

const handleLogout = () => {
setIsLoggedIn(false);
setCurrentScreen('home');
setEmail('');
setPassword('');
setAuthStep('credentials');
};

// -----------------------
// Навигация және ойындарды дайындау
// -----------------------
const setupMathGame = () => {
const createNumber = () => {
if (settings.mathLevel === 'easy') {
return Math.floor(Math.random() * 10) + 1;
}
if (settings.mathLevel === 'medium') {
return Math.floor(Math.random() * 16) + 5; // 5-20
}
return Math.floor(Math.random() * 41) + 10; // 10-50
};

text
let a = createNumber(); let b = createNumber(); let op = '+'; if (settings.mathLevel === 'easy') { op = '+'; } else { op = Math.random() < 0.5 ? '+' : '-'; } let correct; if (op === '-') { if (a < b) [a, b] = [b, a]; correct = a - b; } else { correct = a + b; } const options = new Set([correct]); const variation = settings.mathLevel === 'hard' ? 10 : 5; while (options.size < 4) { const fake = correct + (Math.floor(Math.random() * (variation * 2 + 1)) - variation); if (fake > 0 && fake !== correct) options.add(fake); } const optionsArray = Array.from(options); optionsArray.sort(() => Math.random() - 0.5); setGameA(a); setGameB(b); setGameOp(op); setGameOptions(optionsArray); setGameMessage('');

};

const setupAnimalRound = () => {
const correct = ANIMAL_QUIZ[Math.floor(Math.random() * ANIMAL_QUIZ.length)];
const shuffled = [...ANIMAL_QUIZ].sort(() => Math.random() - 0.5);
const options = [correct];

text
for (const item of shuffled) { if (options.length >= 3) break; if (item.id !== correct.id) options.push(item); } options.sort(() => Math.random() - 0.5); setAnimalQuestion(correct); setAnimalOptions(options); setAnimalMessage('');

};

const resetQuiz = () => {
setQuizIndex(0);
setQuizCorrect(0);
setQuizFinished(false);
};

const resetStory = () => {
setStoryNodeId('start');
setStoryEnded(false);
};

const goToScreen = (screenName) => {
if (screenName === 'games') {
setupMathGame();
}
if (screenName === 'quiz') {
resetQuiz();
}
if (screenName === 'animals') {
setupAnimalRound();
}
if (screenName === 'story') {
resetStory();
}
setCurrentScreen(screenName);
};

// -----------------------
// Дауыспен оқу
// -----------------------
const handleSpeakReading = () => {
speakIfEnabled(READING_TEXT);
completeMission('readText');
};

const handleStopSpeak = () => {
Speech.stop();
};

// -----------------------
// Математикалық ойын
// -----------------------
const handleGameAnswer = (value) => {
const correct = gameOp === '-' ? gameA - gameB : gameA + gameB;
if (value === correct) {
const msg = 'Керемет! Дұрыс жауап! ⭐';
setGameMessage(msg);
speakIfEnabled(msg);
addProgress({ gamesDelta: 1, starsDelta: 1 });
completeMission('playGame');
setTimeout(setupMathGame, 700);
} else {
const msg = 'Әлі де тырысып көр! Қолыңнан келеді!';
setGameMessage(msg);
speakIfEnabled(msg);
}
};

// -----------------------
// Тест
// -----------------------
const handleQuizAnswer = (index) => {
const currentQ = QUIZ_DATA[quizIndex];
if (index === currentQ.correctIndex) {
setQuizCorrect((prev) => prev + 1);
}

text
if (quizIndex + 1 < QUIZ_DATA.length) { setQuizIndex((prev) => prev + 1); } else { setQuizFinished(true); addProgress({ testsDelta: 1, starsDelta: QUIZ_DATA.length, }); completeMission('doQuiz'); }

};

// -----------------------
// Камера + AI "сурет тану"
// -----------------------
const openCamera = async () => {
try {
let permission = cameraPermission;
if (!permission || !permission.granted) {
const result = await requestCameraPermission();
permission = result;
}

text
if (!permission || !permission.granted) { Alert.alert('Рұқсат жоқ', 'Камераға рұқсат беру қажет.'); return; } setCameraOpen(true); setAiResult(''); } catch (e) { Alert.alert('Қате', 'Камера рұқсатын сұрау кезінде қате кетті.'); }

};

const handleTakePicture = async () => {
if (!cameraRef.current) return;
try {
setAiLoading(true);
const photo = await cameraRef.current.takePictureAsync({
base64: true,
quality: 0.3,
});

text
// Демо режим – кездейсоқ "AI болжамы" const labels = ['мысық', 'ит', 'кітап', 'ойыншық', 'адам', 'доп']; const randomLabel = labels[Math.floor(Math.random() * labels.length)]; const text = `AI болжамы: ${randomLabel}. Бұл тек демо, нақты модельмен алмастыруға болады.`; console.log('demo photo base64 length:', photo?.base64?.length || 'no-base64'); setAiResult(text); speakIfEnabled(text); } catch (e) { Alert.alert('Қате', 'Сурет түсіру кезінде қате кетті.'); } finally { setAiLoading(false); setCameraOpen(false); }

};

// -----------------------
// Жануарлар ойыны
// -----------------------
const handleAnimalAnswer = (animalId) => {
if (!animalQuestion) return;
if (animalId === animalQuestion.id) {
const text = Дұрыс! Бұл ${animalQuestion.name}.;
setAnimalMessage(text + ' ⭐');
speakIfEnabled(text);
addProgress({ gamesDelta: 1, starsDelta: 1 });
setTimeout(setupAnimalRound, 800);
} else {
const text = 'Сәл қателестік. Қайта байқап көрейік!';
setAnimalMessage(text);
speakIfEnabled(text);
}
};

// -----------------------
// Көңіл-күй логикасы
// -----------------------
const handleMoodSelect = (option) => {
const today = getTodayKey();
if (moodToday === option.id) {
const text = ${option.emoji} Көңіл-күйің қазірдің өзінде "${option.label}".;
setMoodMessage(text);
speakIfEnabled(text);
} else {
setMoodToday(option.id);
const text = ${option.emoji} Бүгінгі көңіл-күйің: ${option.label}. Жарайсың!;
setMoodMessage(text + ' ⭐');
speakIfEnabled(text);
addProgress({ starsDelta: 1 });
completeMission('chooseMood');
AsyncStorage.setItem(
'aiKidsMood',
JSON.stringify({ date: today, moodId: option.id }),
).catch((err) => console.log('Save mood error', err));
}
};

// -----------------------
// Хикая логикасы
// -----------------------
const handleStoryChoice = (choice) => {
if (choice.starsDelta) {
addProgress({ starsDelta: choice.starsDelta });
}
if (choice.nextId && STORY_NODES[choice.nextId]) {
const nextNode = STORY_NODES[choice.nextId];
setStoryNodeId(choice.nextId);
setStoryEnded(!!nextNode.isEnd);
if (nextNode.isEnd) {
completeMission('storyPlay');
}
} else {
setStoryEnded(true);
completeMission('storyPlay');
}
};

const toggleBreathing = () => {
setBreathingPlaying((prev) => !prev);
};

// -----------------------
// Экрандар
// -----------------------
const renderAuth = () => (
<Screen screenKey="auth">
<FadeInView>
<Text style={styles.appTitle}>AI Kids Learning</Text>
<Text style={styles.appSubtitle}>
Заманауи, қауіпсіз, қызықты оқу-ойын алаңы
</Text>
</FadeInView>

text
<FadeInView delay={80}> <BouncyCharacters /> </FadeInView> <FadeInView delay={160}> <WelcomeCarousel /> </FadeInView> <View style={styles.authSwitchRow}> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'login' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('login'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'login' && styles.authSwitchTextActive, ]} > Кіру </Text> </TouchableOpacity> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'register' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('register'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'register' && styles.authSwitchTextActive, ]} > Тіркелу </Text> </TouchableOpacity> </View> <Card> {authMode === 'register' && ( <> <Text style={styles.sectionTitle}>Тіркелу</Text> <TextInput placeholder="Бала аты" style={styles.input} value={childName} onChangeText={setChildName} /> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <TextInput placeholder="Қауіпсіздік сөзі (3FA үшін)" style={styles.input} value={secretWord} onChangeText={setSecretWord} /> <PrimaryButton title="Тіркелу" onPress={handleRegister} /> <Text style={styles.helperText}> Қауіпсіздік сөзі – 3-ші фактор (3FA) ретінде қолданылады. </Text> </> )} {authMode === 'login' && ( <> {authStep === 'credentials' && ( <> <Text style={styles.sectionTitle}>Кіру (1-фактор)</Text> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <PrimaryButton title="Кіру" onPress={handleLoginCredentials} /> <Text style={styles.helperText}>1-фактор — логин және құпия сөз.</Text> </> )} {authStep === '2fa' && ( <> <Text style={styles.sectionTitle}>2FA – код енгізу</Text> <TextInput placeholder="SMS / E-mail код" style={styles.input} value={twoFaCode} onChangeText={setTwoFaCode} keyboardType="numeric" /> <PrimaryButton title="Кодты растау" onPress={handleCheck2FA} /> <Text style={styles.helperText}> 2-фактор — бір реттік код. Бұл демо режим: код Alert арқылы көрсетілді. </Text> </> )} {authStep === '3fa' && ( <> <Text style={styles.sectionTitle}>3FA – биометрия немесе құпия сөз</Text> <TextInput placeholder="Қауіпсіздік сөзі (егер биометрия жоқ болса)" style={styles.input} value={secretWordLogin} onChangeText={setSecretWordLogin} /> <PrimaryButton title="3FA тексеру" onPress={handleThreeFA} /> <Text style={styles.helperText}> 3-фактор — биометрия (Face ID / Fingerprint) немесе алдын ала сақталған қауіпсіздік сөзі. </Text> </> )} </> )} </Card> </Screen>

);

const renderHome = () => {
const currentLevel = Math.floor(progress.stars / 5) + 1;
const starsInLevel = progress.stars % 5;
const remaining = starsInLevel === 0 ? 5 : 5 - starsInLevel;
const activeCharacter =
CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0];
const today = getTodayKey();

text
return ( <Screen screenKey="home"> <View style={styles.headerRow}> <View style={{ flex: 1 }}> <Text style={styles.homeTitle}> Сәлем, {savedUser?.childName || 'досым'}! 👋 </Text> <Text style={styles.homeSubtitleSmall}> Жұлдыздар: {progress.stars} | Деңгей: {currentLevel} </Text> <Text style={styles.homeSubtitleSmall}> Бүгін: {today} | Сенің досың: {activeCharacter.emoji} {activeCharacter.label} </Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.homeSubtitleSmall}> Келесі деңгейге дейін: {remaining} жұлдыз </Text> </View> <SecondaryButton title="Шығу" onPress={handleLogout} /> </View> <BouncyCharacters small /> <Text style={styles.appSubtitle}> Робо-Дос пен достары сені күтіп тұр! Қай әлемге барамыз? 🌈 </Text> <Card> <Text style={styles.sectionTitle}>📚 Оқу мен білім</Text> <PrimaryButton title="🎧 Дауыспен оқу" onPress={() => goToScreen('reading')} /> <PrimaryButton title="🎮 Математикалық ойын" onPress={() => goToScreen('games')} /> <PrimaryButton title="📝 Тест" onPress={() => goToScreen('quiz')} /> <PrimaryButton title="🔤 Қазақ әліпбиі" onPress={() => goToScreen('alphabet')} /> <PrimaryButton title="🎨 Түстер" onPress={() => goToScreen('colors')} /> </Card> <Card> <Text style={styles.sectionTitle}>🎡 Қызық аймақ</Text> <PrimaryButton title="🎭 Көңіл-күй күнделігі" onPress={() => goToScreen('mood')} /> <PrimaryButton title="🌬️ Тыныс алу ойыны" onPress={() => goToScreen('calm')} /> <PrimaryButton title="📚 Таңдау хикаясы" onPress={() => goToScreen('story')} /> <PrimaryButton title="🐾 Жануарларды таны" onPress={() => goToScreen('animals')} /> <PrimaryButton title="📷 Сурет тану (AI)" onPress={() => goToScreen('image')} /> </Card> <Card> <Text style={styles.sectionTitle}>⭐ Прогресс және миссиялар</Text> <Text style={styles.progressText}>Ойындар: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жиналған жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}>Деңгей: {currentLevel}-деңгей 🌈</Text> <View style={{ height: 8 }} /> <PrimaryButton title="🎯 Күнделікті миссиялар" onPress={() => goToScreen('missions')} /> <PrimaryButton title="🏆 Жетістіктер" onPress={() => goToScreen('achievements')} /> <PrimaryButton title="⚙️ Баптаулар" onPress={() => goToScreen('settings')} /> </Card> </Screen> );

};

const renderReading = () => (
<Screen screenKey="reading">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🎧 Дауыспен оқу</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>
<Card>
<Text style={styles.sectionTitle}>Мәтін</Text>
<Text style={styles.readingText}>{READING_TEXT}</Text>
<PrimaryButton title="Дауыспен оқу" onPress={handleSpeakReading} />
<SecondaryButton title="Тоқтату" onPress={handleStopSpeak} light />
<Text style={styles.helperText}>
Бұл жерде AI-дың дауыс синтезі (Text-to-Speech) қолданылады.
</Text>
</Card>
</Screen>
);

const renderGames = () => (
<Screen screenKey="games">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🎮 Математикалық ойын</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>
<Card>
<Text style={styles.sectionTitle}>Мысалды тап!</Text>
<Text style={styles.bigQuestion}>
{gameA} {gameOp} {gameB} = ?
</Text>
<View style={styles.optionsRow}>
{gameOptions.map((opt) => (
<TouchableOpacity
key={opt}
style={styles.optionButton}
onPress={() => handleGameAnswer(opt)}
>
<Text style={styles.optionText}>{opt}</Text>
</TouchableOpacity>
))}
</View>
{gameMessage !== '' && <Text style={styles.gameMessage}>{gameMessage}</Text>}
</Card>
</Screen>
);

const renderQuiz = () => {
const currentQ = QUIZ_DATA[quizIndex];

text
return ( <Screen screenKey="quiz"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📝 Тест</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> {!quizFinished ? ( <> <Text style={styles.sectionTitle}> Сұрақ {quizIndex + 1} / {QUIZ_DATA.length} </Text> <Text style={styles.quizQuestion}>{currentQ.text}</Text> {currentQ.options.map((opt, idx) => ( <TouchableOpacity key={idx} style={styles.optionButtonWide} onPress={() => handleQuizAnswer(idx)} > <Text style={styles.optionText}>{opt}</Text> </TouchableOpacity> ))} </> ) : ( <> <Text style={styles.sectionTitle}>Нәтиже</Text> <Text style={styles.quizResultText}> Дұрыс жауаптар: {quizCorrect} / {QUIZ_DATA.length} </Text> <Text style={styles.helperText}> Жарайсың! Сенің жұлдыздарың прогресске қосылды. </Text> <PrimaryButton title="Қайта тапсыру" onPress={resetQuiz} /> </> )} </Card> </Screen> );

};

const renderProgress = () => {
const currentLevel = Math.floor(progress.stars / 5) + 1;

text
return ( <Screen screenKey="progress"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⭐ Прогресс</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> <Text style={styles.progressText}>Ойындар саны: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер саны: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}> Деңгей: {currentLevel}-деңгей 🌟 </Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.helperText}> Барлық деректер құрылғыда сақталады. Көп жеңіс – көп деңгей! </Text> </Card> </Screen> );

};

const renderImageAI = () => (
<Screen screenKey="image">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>📷 AI – сурет тану</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Камера арқылы объект тану</Text> <Text style={styles.helperText}> Камераны ашып, бір затты түсір. Робо-Дос сенімен бірге не екенін болжайды (демо режим, шынайы модельмен ауыстыруға болады). </Text> <PrimaryButton title="Камераны ашу" onPress={openCamera} /> {aiLoading && ( <View style={styles.loadingRow}> <ActivityIndicator size="large" /> <Text style={styles.helperText}>AI суретті талдап жатыр...</Text> </View> )} {aiResult !== '' && <Text style={styles.aiResultText}>{aiResult}</Text>} </Card> {cameraOpen && ( <Card> <Text style={styles.sectionTitle}>Камера</Text> <View style={styles.cameraBox}> <CameraView style={styles.camera} ref={cameraRef} facing="back" /> </View> <PrimaryButton title="Суретке түсіру" onPress={handleTakePicture} /> </Card> )} </Screen>

);

const renderAnimals = () => (
<Screen screenKey="animals">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🐾 Жануарларды таны</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Бұл қандай жануар?</Text> <View style={styles.animalEmojiBox}> <Text style={styles.animalEmoji}>{animalQuestion?.emoji}</Text> </View> <Text style={styles.helperText}>Төмендегі сөздердің ішінен дұрыс жануарды таңда.</Text> <View style={styles.optionsRow}> {animalOptions.map((animal) => ( <TouchableOpacity key={animal.id} style={styles.optionButton} onPress={() => handleAnimalAnswer(animal.id)} > <Text style={styles.optionText}>{animal.name}</Text> </TouchableOpacity> ))} </View> {animalMessage !== '' && <Text style={styles.gameMessage}>{animalMessage}</Text>} </Card> </Screen>

);

const renderAlphabet = () => (
<Screen screenKey="alphabet">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🔤 Қазақ әліпбиі</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Әріпті бас – дауыстап айтсын</Text> <Text style={styles.helperText}> Әр түймені басқанда, әріп пен мысал сөз қазақ тілінде дауыстап айтылады. </Text> <View style={styles.alphabetGrid}> {KAZAKH_ALPHABET.map((item) => ( <TouchableOpacity key={item.letter} style={styles.alphabetButton} onPress={() => speakIfEnabled(`${item.letter} – ${item.word}`)} > <Text style={styles.alphabetLetter}>{item.letter}</Text> <Text style={styles.alphabetWord}>{item.word}</Text> </TouchableOpacity> ))} </View> </Card> </Screen>

);

const renderColors = () => (
<Screen screenKey="colors">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🎨 Түстер</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Түстерді үйренейік</Text> <Text style={styles.helperText}> Әр түсті басқанда, оның атауы мен қысқа сипаттамасы дауыстап айтылады. </Text> <View style={styles.colorsGrid}> {COLOR_LESSONS.map((color) => ( <TouchableOpacity key={color.id} style={[styles.colorCard, { backgroundColor: color.bg }]} onPress={() => speakIfEnabled(`${color.name}. ${color.text}`)} > <Text style={styles.colorName}>{color.name}</Text> <Text style={styles.colorText}>{color.text}</Text> </TouchableOpacity> ))} </View> </Card> </Screen>

);

const renderMissions = () => (
<Screen screenKey="missions">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🎯 Күнделікті миссиялар</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}> Бүгінгі тапсырмалар ({missions.date || getTodayKey()}) </Text> {missions.items && missions.items.length > 0 ? ( missions.items.map((m) => ( <View key={m.id} style={styles.missionRow}> <Text style={styles.missionIcon}>{m.done ? '⭐' : '⬜️'}</Text> <Text style={[styles.missionText, m.done && styles.missionTextDone]}> {m.title} </Text> </View> )) ) : ( <Text style={styles.helperText}> Миссиялар жүктелуде немесе бүгінге арналған тапсырмалар жоқ. </Text> )} <PrimaryButton title="Сыйлықты алу" onPress={collectMissionReward} /> <Text style={styles.helperText}> Барлық миссияларды орындасаң, қосымша жұлдыздар беріледі. Миссиялар әр күн сайын жаңарады. </Text> </Card> </Screen>

);

const renderAchievements = () => (
<Screen screenKey="achievements">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🏆 Жетістіктер</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> {ACHIEVEMENTS.map((ach) => { const value = ach.type === 'gamesPlayed' ? progress.gamesPlayed : ach.type === 'testsPassed' ? progress.testsPassed : progress.stars; const unlocked = value >= ach.threshold; return ( <View key={ach.id} style={styles.achievementRow}> <Text style={styles.achievementIcon}>{unlocked ? '✅' : '🔒'}</Text> <View style={{ flex: 1 }}> <Text style={styles.achievementTitle}>{ach.title}</Text> <Text style={styles.achievementDesc}> {ach.description} ({Math.min(value, ach.threshold)}/{ach.threshold}) </Text> </View> </View> ); })} <Text style={styles.helperText}> Жұлдыздар, ойындар және тесттер саны көбейген сайын жаңа жетістіктер ашылады. </Text> </Card> </Screen>

);

const renderSettings = () => {
const activeCharacter =
CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0];

text
return ( <Screen screenKey="settings"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⚙️ Баптаулар</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Дыбыс</Text> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Дауыстық көмек</Text> <Switch value={settings.voiceHints} onValueChange={(value) => updateSettings({ voiceHints: value })} /> </View> <Text style={styles.helperText}> Егер өшірсең, қолданба ештеңе сөйлемейді (мәтін де, кеңестер де). </Text> </Card> <Card> <Text style={styles.sectionTitle}>Математика деңгейі</Text> <View style={styles.difficultyRow}> {[ { id: 'easy', label: 'Жеңіл' }, { id: 'medium', label: 'Орта' }, { id: 'hard', label: 'Қиын' }, ].map((lvl) => ( <TouchableOpacity key={lvl.id} style={[ styles.difficultyButton, settings.mathLevel === lvl.id && styles.difficultyButtonActive, ]} onPress={() => updateSettings({ mathLevel: lvl.id })} > <Text style={[ styles.difficultyText, settings.mathLevel === lvl.id && styles.difficultyTextActive, ]} > {lvl.label} </Text> </TouchableOpacity> ))} </View> <Text style={styles.helperText}> Деңгей жоғарылаған сайын сандар да үлкенірек болады, кейде азайту амалы да қосылады. </Text> </Card> <Card> <Text style={styles.sectionTitle}>Кейіпкер таңдау</Text> <Text style={styles.helperText}> Таңдалған кейіпкер басты экранда аватар ретінде көрсетіледі, ал бүкіл қолданбаның түс схемасы соған сай өзгереді. </Text> <View style={styles.charactersRow}> {CHARACTERS.map((ch) => { const isActive = ch.id === settings.chosenCharacterId; return ( <TouchableOpacity key={ch.id} style={[styles.characterBubble, isActive && styles.characterBubbleActive]} onPress={() => updateSettings({ chosenCharacterId: ch.id })} > <Text style={styles.characterEmoji}>{ch.emoji}</Text> <Text style={styles.characterLabel}>{ch.label}</Text> </TouchableOpacity> ); })} </View> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Таңдалған кейіпкер</Text> <Text style={styles.settingsValue}> {activeCharacter.emoji} {activeCharacter.label} </Text> </View> </Card> </Screen> );

};

const renderMood = () => (
<Screen screenKey="mood">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🎭 Көңіл-күй күнделігі</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Бүгін өзіңді қалай сезініп тұрсың?</Text> <Text style={styles.helperText}> Бір эмоцияны таңда. Робо-Дос сенің көңіл-күйіңді еске сақтайды. </Text> <View style={styles.moodGrid}> {MOOD_OPTIONS.map((m) => { const active = moodToday === m.id; return ( <TouchableOpacity key={m.id} style={[ styles.moodCard, { backgroundColor: m.color }, active && styles.moodCardActive, ]} onPress={() => handleMoodSelect(m)} > <Text style={styles.moodEmoji}>{m.emoji}</Text> <Text style={styles.moodLabel}>{m.label}</Text> </TouchableOpacity> ); })} </View> {moodMessage !== '' && <Text style={styles.gameMessage}>{moodMessage}</Text>} </Card> </Screen>

);

const renderCalm = () => (
<Screen screenKey="calm">
<View style={styles.headerRow}>
<Text style={styles.homeTitle}>🌬️ Тыныс алу ойыны</Text>
<SecondaryButton title="Артқа" onPress={() => goToScreen('home')} />
</View>

text
<Card> <Text style={styles.sectionTitle}>Спокой дем ал, дем шығар</Text> <Text style={styles.helperText}> Шеңбер үлкейгенде – дем ал, кішірейгенде – дем шығар. Бірнеше рет қайтала. </Text> <BreathingCircle playing={breathingPlaying} /> <Text style={styles.breathingLabel}>{breathingLabel}</Text> <PrimaryButton title={breathingPlaying ? 'Тоқтату' : 'Бастау'} onPress={toggleBreathing} /> {breathingMessage !== '' && ( <Text style={styles.gameMessage}>{breathingMessage}</Text> )} </Card> </Screen>

);

const renderStory = () => {
const node = STORY_NODES[storyNodeId] || STORY_NODES.start;

text
return ( <Screen screenKey="story"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📚 Таңдау хикаясы</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>{node.title}</Text> <Text style={styles.readingText}>{node.text}</Text> {!node.isEnd && node.choices && node.choices.length > 0 && ( <> {node.choices.map((choice) => ( <TouchableOpacity key={choice.id} style={styles.optionButtonWide} onPress={() => handleStoryChoice(choice)} > <Text style={styles.optionText}>{choice.text}</Text> </TouchableOpacity> ))} </> )} {node.isEnd && ( <> <Text style={styles.helperText}> Хикая аяқталды. Таңдауларың үшін рахмет! Тағы да ойнағың келсе – басынан бастауға болады. </Text> <PrimaryButton title="Қайта бастау" onPress={resetStory} /> </> )} </Card> </Screen> );

};

// -----------------------
// Негізгі render
// -----------------------
return (
<ThemeContext.Provider value={themeValue}>
<SafeAreaView style={styles.safe}>
{!isLoggedIn
? renderAuth()
: currentScreen === 'home'
? renderHome()
: currentScreen === 'reading'
? renderReading()
: currentScreen === 'games'
? renderGames()
: currentScreen === 'quiz'
? renderQuiz()
: currentScreen === 'progress'
? renderProgress()
: currentScreen === 'image'
? renderImageAI()
: currentScreen === 'animals'
? renderAnimals()
: currentScreen === 'alphabet'
? renderAlphabet()
: currentScreen === 'colors'
? renderColors()
: currentScreen === 'missions'
? renderMissions()
: currentScreen === 'achievements'
? renderAchievements()
: currentScreen === 'settings'
? renderSettings()
: currentScreen === 'mood'
? renderMood()
: currentScreen === 'calm'
? renderCalm()
: currentScreen === 'story'
? renderStory()
: renderHome()}
</SafeAreaView>
</ThemeContext.Provider>
);
}

// -----------------------
// Стильдер – заманауи, жұмсақ дизайн
// -----------------------
const styles = StyleSheet.create({
safe: {
flex: 1,
backgroundColor: '#000',
},
gradient: {
flex: 1,
},
scroll: {
padding: 20,
paddingBottom: 40,
},
appTitle: {
fontSize: 30,
fontWeight: '800',
color: '#fff',
textAlign: 'center',
marginBottom: 8,
letterSpacing: 0.5,
},
appSubtitle: {
fontSize: 16,
color: 'rgba(255,255,255,0.9)',
textAlign: 'center',
marginBottom: 16,
},
homeTitle: {
fontSize: 22,
fontWeight: '700',
color: '#fff',
},
homeSubtitleSmall: {
fontSize: 14,
color: 'rgba(255,255,255,0.9)',
marginTop: 4,
},
headerRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 16,
gap: 8,
justifyContent: 'space-between',
},
card: {
backgroundColor: 'rgba(255,255,255,0.96)',
borderRadius: 22,
padding: 18,
marginBottom: 16,
shadowColor: '#000',
shadowOpacity: 0.18,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
elevation: 5,
},
sectionTitle: {
fontSize: 18,
fontWeight: '700',
marginBottom: 10,
color: '#222',
},
primaryButton: {
backgroundColor: '#ff7a59',
paddingVertical: 13,
borderRadius: 18,
alignItems: 'center',
marginTop: 8,
},
primaryButtonText: {
color: '#fff',
fontSize: 15,
fontWeight: '700',
},
secondaryButton: {
borderColor: 'rgba(255,255,255,0.9)',
borderWidth: 1,
paddingVertical: 8,
paddingHorizontal: 12,
borderRadius: 20,
},
secondaryButtonLight: {
borderColor: '#ccc',
backgroundColor: '#fff',
},
secondaryButtonText: {
color: '#fff',
fontSize: 14,
fontWeight: '600',
},
secondaryButtonTextDark: {
color: '#333',
},
input: {
backgroundColor: '#f4f4f8',
borderRadius: 14,
paddingHorizontal: 14,
paddingVertical: 9,
marginBottom: 10,
fontSize: 14,
borderWidth: 1,
borderColor: '#e0e0e0',
},
helperText: {
fontSize: 12,
color: '#777',
marginTop: 8,
},
authSwitchRow: {
flexDirection: 'row',
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 30,
padding: 4,
marginBottom: 16,
alignSelf: 'center',
},
authSwitchButton: {
flex: 1,
paddingVertical: 8,
borderRadius: 26,
alignItems: 'center',
},
authSwitchButtonActive: {
backgroundColor: 'rgba(255,255,255,0.95)',
},
authSwitchText: {
color: 'rgba(255,255,255,0.9)',
fontWeight: '600',
},
authSwitchTextActive: {
color: '#333',
},
readingText: {
fontSize: 16,
lineHeight: 22,
color: '#444',
marginBottom: 12,
},
bigQuestion: {
fontSize: 30,
fontWeight: '800',
textAlign: 'center',
marginBottom: 12,
color: '#333',
},
optionsRow: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
gap: 8,
},
optionButton: {
backgroundColor: '#fff3cd',
paddingVertical: 10,
paddingHorizontal: 14,
borderRadius: 14,
marginTop: 8,
flexBasis: '48%',
alignItems: 'center',
},
optionButtonWide: {
backgroundColor: '#fff3cd',
paddingVertical: 10,
paddingHorizontal: 14,
borderRadius: 14,
marginTop: 8,
},
optionText: {
fontSize: 14,
fontWeight: '600',
color: '#444',
},
gameMessage: {
marginTop: 12,
fontSize: 15,
textAlign: 'center',
color: '#333',
},
quizQuestion: {
fontSize: 16,
marginBottom: 8,
color: '#333',
},
quizResultText: {
fontSize: 18,
fontWeight: '700',
marginBottom: 8,
color: '#333',
},
progressText: {
fontSize: 15,
marginBottom: 4,
color: '#333',
},
cameraBox: {
borderRadius: 20,
overflow: 'hidden',
height: 220,
marginBottom: 12,
},
camera: {
flex: 1,
},
loadingRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
marginTop: 12,
},
aiResultText: {
marginTop: 10,
fontSize: 15,
fontWeight: '600',
color: '#333',
},
animalEmojiBox: {
alignItems: 'center',
marginBottom: 12,
},
animalEmoji: {
fontSize: 86,
},
alphabetGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
gap: 8,
marginTop: 12,
},
alphabetButton: {
width: '23%',
backgroundColor: '#e3f2fd',
borderRadius: 12,
paddingVertical: 10,
paddingHorizontal: 4,
alignItems: 'center',
},
alphabetLetter: {
fontSize: 20,
fontWeight: '800',
color: '#1e88e5',
},
alphabetWord: {
fontSize: 11,
color: '#555',
marginTop: 4,
textAlign: 'center',
},
colorsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
marginTop: 12,
},
colorCard: {
flexBasis: '48%',
borderRadius: 18,
paddingVertical: 14,
paddingHorizontal: 10,
},
colorName: {
fontSize: 18,
fontWeight: '800',
color: '#fff',
marginBottom: 6,
},
colorText: {
fontSize: 13,
color: 'rgba(255,255,255,0.95)',
},

// Кейіпкерлер
charactersRow: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'flex-end',
marginBottom: 12,
gap: 10,
flexWrap: 'wrap',
},
characterBubble: {
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 999,
alignItems: 'center',
justifyContent: 'center',
minWidth: 90,
shadowColor: '#000',
shadowOpacity: 0.15,
shadowRadius: 6,
shadowOffset: { width: 0, height: 3 },
elevation: 4,
},
characterBubbleSmall: {
paddingVertical: 6,
paddingHorizontal: 10,
minWidth: 70,
},
characterBubbleActive: {
borderWidth: 2,
borderColor: '#fff',
},
characterEmoji: {
fontSize: 26,
},
characterLabel: {
marginTop: 4,
fontSize: 11,
fontWeight: '700',
color: '#fff',
},

// Welcome slider
carouselContainer: {
height: 190,
marginBottom: 12,
},
carouselCard: {
flex: 1,
marginHorizontal: 12,
borderRadius: 20,
paddingVertical: 16,
paddingHorizontal: 12,
alignItems: 'center',
justifyContent: 'center',
},
carouselEmoji: {
fontSize: 48,
marginBottom: 4,
},
carouselTitle: {
fontSize: 20,
fontWeight: '800',
color: '#fff',
marginTop: 4,
},
carouselText: {
fontSize: 13,
color: 'rgba(255,255,255,0.95)',
textAlign: 'center',
marginTop: 6,
},
carouselDots: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginTop: 8,
},
carouselDot: {
height: 8,
borderRadius: 4,
backgroundColor: 'rgba(255,255,255,0.9)',
marginHorizontal: 4,
},

// Level progress bar
levelBarOuter: {
height: 10,
borderRadius: 999,
backgroundColor: 'rgba(255,255,255,0.35)',
overflow: 'hidden',
marginTop: 6,
},
levelBarInner: {
height: '100%',
borderRadius: 999,
backgroundColor: '#ffe66d',
},

// Daily missions
missionRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
gap: 8,
},
missionIcon: {
fontSize: 18,
},
missionText: {
fontSize: 14,
color: '#333',
},
missionTextDone: {
textDecorationLine: 'line-through',
color: '#999',
},

// Achievements
achievementRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
gap: 10,
},
achievementIcon: {
fontSize: 20,
},
achievementTitle: {
fontSize: 15,
fontWeight: '700',
color: '#333',
},
achievementDesc: {
fontSize: 12,
color: '#666',
marginTop: 2,
},

// Settings
settingsRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 8,
},
settingsLabel: {
fontSize: 14,
color: '#333',
fontWeight: '600',
},
settingsValue: {
fontSize: 14,
color: '#555',
},
difficultyRow: {
flexDirection: 'row',
gap: 8,
marginTop: 8,
},
difficultyButton: {
flex: 1,
paddingVertical: 8,
borderRadius: 20,
borderWidth: 1,
borderColor: '#ddd',
alignItems: 'center',
backgroundColor: '#f7f7f7',
},
difficultyButtonActive: {
backgroundColor: '#ff7a59',
borderColor: '#ff7a59',
},
difficultyText: {
fontSize: 13,
color: '#555',
fontWeight: '600',
},
difficultyTextActive: {
color: '#fff',
},

// Mood
moodGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
marginTop: 12,
},
moodCard: {
flexBasis: '48%',
borderRadius: 18,
paddingVertical: 18,
paddingHorizontal: 10,
alignItems: 'center',
},
moodCardActive: {
borderWidth: 2,
borderColor: '#fff',
},
moodEmoji: {
fontSize: 30,
},
moodLabel: {
marginTop: 4,
fontSize: 14,
fontWeight: '700',
color: '#333',
},

// Breathing
breathingWrapper: {
alignItems: 'center',
marginVertical: 16,
},
breathingCircle: {
width: 140,
height: 140,
borderRadius: 70,
borderWidth: 4,
borderColor: 'rgba(255,255,255,0.8)',
backgroundColor: 'rgba(255,255,255,0.2)',
},
breathingLabel: {
fontSize: 18,
fontWeight: '700',
textAlign: 'center',
marginBottom: 8,
color: '#333',
},
});

нужно поменять тему на детейлинг центр aer019

Thought

Updating app theme to fit detailing center

The user wants the app’s theme to match a car detailing center, "aer019". I’ll assume they prefer colors like deep blue or teal with neon green accents and dark charcoal gradients. I’ll suggest replacing the existing THEMES definition or modifying it to reflect this. I’ll also check the CHARACTERS section for possible rebranding.

Focusing on theme changes for app

The user only mentioned changing the theme, but I could show how to add a character for “AER019” as an option, though it might be overkill. I’ll propose updates for the colors and minor font changes in the THEMES object and the default ThemeContext. I’ll keep the copy intact, providing a patch instead of rewriting the whole file. Since they wrote in Russian, I'll respond in Russian as well.

Planning theme updates for detailing center

I’ll start by updating the ThemeContext to use auto-detailing colors, like neon cyan and amber for progress, with darker gradient backgrounds. For the THEMES object, I’d keep the existing character themes but update the colors and gradients to match the brand. I’ll take extra care to ensure consistency.

Updating the 'robot' theme for brand consistency

To keep things simple, I'll update the robot theme's screenGradients to match the auto-detailing theme while keeping the overall structure. The other themes like bunny and lion can be updated or left as they are since they'll only show if the user selects them. I'll default to the 'robot' theme, updating its colors for a neon/industrial look. For instance, use dark gradients for defaults and teal and blue for highlights.

Proposing theme updates for "AER019 Detailing"

I’ll help the user switch to a cohesive "AER019 Detailing" theme. First, update the ThemeContext with new brand colors like cyan and amber, followed by the screenGradients for all screens.

Then, I'd suggest replacing the THEMES constant with a single palette, while keeping the option to change fonts or characters. I'll show a code snippet with these changes, keeping it simple for them to copy.

Thought for 1m 50s

Answer

Сейчас у тебя тема «детская / мультики» (градиенты в THEMES + дефолтные цвета в ThemeContext).
Сделаем тёмную «детейлинг-центр AER019» тему: тёмный фон, неоновый бирюзовый акцент, жёлтый прогресс.

Ниже — конкретные куски кода, которые можно просто заменить.


1. Обнови ThemeContext (глобальные цвета по умолчанию)

Найди в самом начале файла:

js
const ThemeContext = createContext({ primaryColor: '#ff7a59', levelColor: '#ffe66d', getScreenColors: () => ['#0f2027', '#203a43', '#2c5364'], });

Замени на:

js
const ThemeContext = createContext({ // Основной бренд-цвет AER019 (неоновый) primaryColor: '#00E0FF', // Цвет уровня / прогресса (тёплый жёлтый, как подсветка) levelColor: '#FFD54F', // Общий фон – тёмный, «гараж/детейлинг» getScreenColors: () => ['#020617', '#050816', '#0B1120'], });

2. Поменяй тему THEMES.robot на AER019

Найди объект THEMES = { ... } и полностью замени его на этот:

js
const THEMES = { // Основная тема – AER019 Detailing robot: { primaryColor: '#00E0FF', // акцентные кнопки, главный цвет levelColor: '#FFD54F', // прогресс / уровень screenGradients: { // Общий фон (если для экрана нет отдельного градиента) default: ['#020617', '#050816', '#0B1120'], // Авторизация auth: ['#020617', '#0B1120'], // Главный экран home: ['#020617', '#050816', '#0B1120'], // Дауыспен оқу – спокойный, но фирменный reading: ['#020617', '#0369A1'], // Математический ойын – яркий, «неон в гараже» games: ['#020617', '#0B1120', '#22C55E'], // Тесттер quiz: ['#020617', '#0B1120', '#00E0FF'], // Прогресс progress: ['#020617', '#0B1120', '#FFD54F'], // Камера / AI image: ['#020617', '#0B1120', '#00E0FF'], // Жануарлар animals: ['#020617', '#0B1120', '#22C55E'], // Әліпби alphabet: ['#020617', '#0369A1'], // Түстер colors: ['#020617', '#0B1120', '#F97316'], // Күнделікті миссиялар missions: ['#020617', '#0B1120', '#22C55E'], // Жетістіктер achievements: ['#020617', '#0B1120', '#FFD54F'], // Баптаулар settings: ['#020617', '#0B1120'], // Көңіл-күй mood: ['#020617', '#0B1120', '#00E0FF'], // Тыныс алу calm: ['#020617', '#0B1120', '#22C55E'], // Хикая story: ['#020617', '#0B1120', '#00E0FF'], }, }, // Остальные персонажи можно оставить, но перекрасить чуть-чуть, // либо вообще не использовать. Для простоты сделаем их вариациями // той же AER019 темы. bunny: { primaryColor: '#00E0FF', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#050816', '#0B1120'], auth: ['#020617', '#0B1120'], home: ['#020617', '#020617', '#0369A1'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#22C55E'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, lion: { primaryColor: '#F97316', // можно сделать «огненный» вариант levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#0B1120', '#111827'], auth: ['#020617', '#111827'], home: ['#020617', '#0B1120', '#F97316'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#F97316'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, };

Теперь все экраны будут на тёмном фоне с неоновыми акцентами — в стиле детейлинг-центра.


3. (Опционально) Переименовать персонажей под AER019

Если хочешь, чтобы и подписи под аватарками были про детейлинг, можно чуть поменять CHARACTERS:

js
const CHARACTERS = [ { id: 'robot', emoji: '🚗', label: 'AER019 Бот', color: '#00E0FF' }, { id: 'bunny', emoji: '🧽', label: 'Detail Master', color: '#F97316' }, { id: 'lion', emoji: '✨', label: 'Shine Pro', color: '#22C55E' }, ];

(Можно написать свои названия на русском/казахском, главное — id не трогать, чтобы логика не ломалась.)


Если хочешь, можем следующим шагом ещё и текст/логотип поменять под бренд (например, AI Kids LearningAER019 Detailing Kids или что-то своё).

Question

да давай дорабатываем мой проект

Question

ты напиши полный новый код с той же логикой как в коде для детейлинга

Thought

Adapting app for detailing center

The user wants the app’s logic and structure to remain the same, but the theme and content should be adapted for a detailing center, like rebranding to "aer019" brand and changing the copy. The app will focus on business tasks like employee training and bookings, keeping features like multi-factor auth and progress tracking. The main change will be in the text, screens, and colors, while the logic flows and structure remain intact.

Adapting app for car-care theme

I'll rebrand the app with a car-care theme and keep the game/quiz structure, possibly for adults. We'll use Russian for the text because it’s more fitting, but I’ll keep some Kazakh if the user still wants it. The core functionality, like the ThemeContext and state logic, will stay the same.

Rebranding constants to match car theme

I’ll adjust constants like WELCOME_SLIDES, text labels, READINGS, QUIZ_DATA, and animals to be car-related. For example, quizzes will be about detailing, texts will mention the center, and animals will represent services or tools. The logic and structure, like arrays with {id, name, emoji}, will stay consistent. It’s important to keep imports, components, and functions intact to avoid breaking the app. I'll regenerate the customized code carefully.

Adjusting original code for AER019 theme

I'll start with the original code and make modifications to fit the AER019 detailing theme.

  • ThemeContext will use AER019's theme.
  • Switch characters to car icons with AER019 labels.
  • Update WELCOME_SLIDES to mention detailing services, like "Умный детейлинг".
  • Replace text strings, such as changing "AI Kids Learning" to "AER019 Detailing". The app flow and screens (auth, home, reading, etc.) will remain unchanged.

Keeping logic intact while rebranding

The user wants the same app logic, so we’ll keep screen names and flows like auth, home, and reading. We can change the UI text to reflect the new focus, for example, "reading" becomes "О компании", but the underlying components and IDs should remain the same. We'll avoid using TypeScript, and ensure proper Russian characters to avoid breaking the JS. We’ll be careful with undefined variables and ensure everything runs smoothly while updating the text and some logic.

Adapting the app for detailing with minimal changes

We need to deliver the full code, sticking to the original structure but adapting it to a car care theme. Instead of kids-related text, we'll focus on detailing services and gamification for training. The game logic might stay, but we’ll replace quiz and animal references with car parts or services. We'll reuse original constants like ANIMAL_QUIZ while adjusting its content. Since the user prefers "same logic," we’ll keep educational aspects intact but rebrand accordingly.

Adapting alphabet for training modules

Instead of redoing the entire alphabet screen, I’ll reinterpret it as an internal training module. I can update the KAZAKH_ALPHABET to use Russian/Latin letters tied to car-related terms like "Д" for "детейлинг." I’ll focus on tweaking key visible elements, like character names and mission titles, while keeping some original constants intact for efficiency. I’ll customize enough to reflect the detailing theme without overcomplicating things.

Integrating theme and adjusting branding

I’ll implement the ThemeContext and THEMES modifications as previously discussed, focusing on creating a consistent detailing theme. I’ll adjust the app title to mention AER019, for consistency with the theme request. The rest of the text will largely remain as is, only slightly customizing for the detailing center vibe.

Rebranding the app and integrating theme

I'll update the app with

Thought for 43s

Answer

Кидаю полностью переписанный App.js с тем же функционалом, но под бренд детейлинг-центра AER019: тёмная гаражная тема, неон, машины и т.п. Логика, стейты, экраны и навигация — такие же, так что просто заменяешь свой файл на этот.

js
import AsyncStorage from '@react-native-async-storage/async-storage'; import { CameraView, useCameraPermissions } from 'expo-camera'; import { LinearGradient } from 'expo-linear-gradient'; import * as LocalAuthentication from 'expo-local-authentication'; import * as Speech from 'expo-speech'; import * as SQLite from 'expo-sqlite'; import { createContext, useContext, useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Alert, Animated, Easing, SafeAreaView, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, useWindowDimensions, View, } from 'react-native'; // ----------------------- // Тема приложения (контекст) – AER019 Detailing // ----------------------- const ThemeContext = createContext({ // Основной бренд-цвет AER019 (неон) primaryColor: '#00E0FF', // Цвет прогресса/уровня levelColor: '#FFD54F', // Общий фон – тёмный гараж getScreenColors: () => ['#020617', '#050816', '#0B1120'], }); // ----------------------- // SQLite DB (қолжетімді болса ғана) // ----------------------- let db = null; try { if (SQLite && typeof SQLite.openDatabase === 'function') { db = SQLite.openDatabase('ai_kids.db'); } else { console.warn('SQLite.openDatabase табылмады. Тек AsyncStorage қолданамыз.'); } } catch (e) { console.warn('SQLite ашу кезінде қате. Тек AsyncStorage қолданамыз.', e); } // ----------------------- // Персонажи / аватары под AER019 // ----------------------- const CHARACTERS = [ { id: 'robot', emoji: '🚗', label: 'AER019 Бот', color: '#00E0FF' }, { id: 'bunny', emoji: '🧽', label: 'Detail Master', color: '#F97316' }, { id: 'lion', emoji: '✨', label: 'Shine Pro', color: '#22C55E' }, ]; // Авторизация экранындағы слайдер const WELCOME_SLIDES = [ { key: 'slide1', title: 'Умный гараж AER019', text: 'Игровой формат обучения уходу за авто, безопасности и технологиям.', emoji: '🚗', background: ['#020617', '#0369A1'], }, { key: 'slide2', title: 'Голосовой помощник', text: 'Читаем текст вслух, подсказываем и мотивируем ребёнка.', emoji: '🎧', background: ['#0B1120', '#00E0FF'], }, { key: 'slide3', title: 'AI-камера в боксе', text: 'Камера и AI помогают «угадывать» предметы вокруг автомобиля.', emoji: '📷', background: ['#020617', '#22C55E'], }, ]; const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity); // ----------------------- // Темы для персонажей – AER019 Detailing // ----------------------- const THEMES = { // Основная тема – AER019 Detailing robot: { primaryColor: '#00E0FF', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#050816', '#0B1120'], auth: ['#020617', '#0B1120'], home: ['#020617', '#050816', '#0B1120'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#22C55E'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, // Вариация темы – немного другие акценты, но тот же стиль bunny: { primaryColor: '#00E0FF', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#050816', '#0B1120'], auth: ['#020617', '#0B1120'], home: ['#020617', '#020617', '#0369A1'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#22C55E'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, // Ещё одна вариация – «огненный» акцент lion: { primaryColor: '#F97316', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#0B1120', '#111827'], auth: ['#020617', '#111827'], home: ['#020617', '#0B1120', '#F97316'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#F97316'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, }; // ----------------------- // Тұрақты деректер // ----------------------- const QUIZ_DATA = [ { text: '1) Детейлинг дегеніміз не?', options: [ 'А) Тек жылдам жуу', 'Ә) Көлікті терең тазалау және қорғау', 'Б) Дөңгелек ауыстыру', 'В) Тек салон иісін жаңарту', ], correctIndex: 1, }, { text: '2) Көлікті не үшін жиі жуу керек?', options: [ 'А) Тек фотоға әдемі шығу үшін', 'Ә) Лак-бояу жабынын қорғау және коррозияны азайту үшін', 'Б) Тек шыныны тазарту үшін', 'В) Мүлде қажет емес', ], correctIndex: 1, }, { text: '3) AER019 орталығында не істейміз?', options: [ 'А) Көлікті кәсіби түрде тазалап, қорғаймыз', 'Ә) Тек ойын ойнаймыз', 'Б) Тек жанармай құямыз', 'В) Тек доңғалақ сатамыз', ], correctIndex: 0, }, ]; const READING_TEXT = 'Бүгін біз AER019 Detailing орталығында көлікке қалай дұрыс қарау керектігін үйренеміз. ' + 'Жасанды интеллект пен ойын форматындағы тапсырмалар балаларға қауіпсіздік пен ұқыптылықты түсінуге көмектеседі.'; // Жануарлар ойыны (оставляем как игру, можно воспринимать как «маскоты») const ANIMAL_QUIZ = [ { id: 'cat', name: 'Мысық', emoji: '🐱' }, { id: 'dog', name: 'Ит', emoji: '🐶' }, { id: 'cow', name: 'Сиыр', emoji: '🐮' }, { id: 'horse', name: 'Жылқы', emoji: '🐴' }, { id: 'chicken', name: 'Тауық', emoji: '🐔' }, { id: 'lion', name: 'Арыстан', emoji: '🦁' }, ]; // Қазақ әліпбиі – можно оставить как развивающий модуль const KAZAKH_ALPHABET = [ { letter: 'А', word: 'авто' }, { letter: 'Ә', word: 'әдемі жылтыр' }, { letter: 'Б', word: 'балапан жүргізуші' }, { letter: 'В', word: 'вагон' }, { letter: 'Г', word: 'гүлді гараж' }, { letter: 'Ғ', word: 'ғалам' }, { letter: 'Д', word: 'детейлинг' }, { letter: 'Е', word: 'ерекше күтім' }, { letter: 'Ж', word: 'жуу' }, { letter: 'З', word: 'жылдамдық (скорость)' }, { letter: 'И', word: 'игі іс' }, { letter: 'Й', word: 'йогурт' }, { letter: 'К', word: 'көлік' }, { letter: 'Қ', word: 'қорғау' }, { letter: 'Л', word: 'лак' }, { letter: 'М', word: 'май алмастыру' }, { letter: 'Н', word: 'нұрлы шамдар' }, { letter: 'Ң', word: 'ңғой (мысал)' }, { letter: 'О', word: 'ойын' }, { letter: 'Ө', word: 'өрік' }, { letter: 'П', word: 'полировка' }, { letter: 'Р', word: 'резина (дөңгелек)' }, { letter: 'С', word: 'сусабын (шампунь)' }, { letter: 'Т', word: 'тазалық' }, { letter: 'У', word: 'унил' }, { letter: 'Ұ', word: 'ұқыптылық' }, { letter: 'Ү', word: 'үйрену' }, { letter: 'Ф', word: 'финиш жабыны' }, { letter: 'Х', word: 'химиялық тазалау' }, { letter: 'Һ', word: 'һә' }, { letter: 'Ц', word: 'цирк' }, { letter: 'Ч', word: 'чехол' }, { letter: 'Ш', word: 'шина' }, { letter: 'Щ', word: 'щетка' }, { letter: 'Ы', word: 'ырыс' }, { letter: 'І', word: 'інді' }, { letter: 'Э', word: 'экран' }, { letter: 'Ю', word: 'юла' }, { letter: 'Я', word: 'якорь' }, ]; // Түстер – перекладываем на авто-тематику const COLOR_LESSONS = [ { id: 'red', name: 'Қызыл', text: 'Қызыл – спорткардың түсі', bg: '#EF4444' }, { id: 'blue', name: 'Көк', text: 'Көк – аспан мен көк седан түсі', bg: '#3B82F6' }, { id: 'green', name: 'Жасыл', text: 'Жасыл – экокардың түсі', bg: '#22C55E' }, { id: 'yellow', name: 'Сары', text: 'Сары – такси мен күннің түсі', bg: '#FACC15' }, ]; // Жетістіктер const ACHIEVEMENTS = [ { id: 'firstStar', type: 'stars', threshold: 1, title: 'Алғашқы кузов', description: '1 жұлдыз жинадың. AER019 әлеміне қош келдің!', }, { id: 'tenStars', type: 'stars', threshold: 10, title: 'Мини-бокс шебері', description: '10 жұлдыз жинадың. Сен нағыз көмекші-механиксің.', }, { id: 'games5', type: 'gamesPlayed', threshold: 5, title: 'Ойын шебері', description: '5 ойын ойнадың. Тапсырмалар саған ұнайды!', }, { id: 'tests3', type: 'testsPassed', threshold: 3, title: 'Білімгер механик', description: '3 тесттен сәтті өттің.', }, { id: 'stars25', type: 'stars', threshold: 25, title: 'Супер гараж', description: '25 жұлдыз жинадың. Сен AER019 жұлдызысың!', }, ]; // Көңіл-күй опциялары const MOOD_OPTIONS = [ { id: 'happy', label: 'Көңілдімін', emoji: '😊', color: '#FACC15' }, { id: 'ok', label: 'Жақсы', emoji: '🙂', color: '#22C55E' }, { id: 'tired', label: 'Шаршадым', emoji: '😴', color: '#A855F7' }, { id: 'sad', label: 'Көңілсіз', emoji: '😕', color: '#3B82F6' }, ]; // Таңдау хикаясы – слегка адаптируем под «жолға шығу» const STORY_NODES = { start: { id: 'start', title: '1-көрініс: Таңғы гараж', text: 'Бүгін AER019 Бот сені оятты. Көлікті күту күні! Не істейсің?', choices: [ { id: 'fast', text: 'Тез жуынуға барып, боксқа дайындаламын', nextId: 'school', starsDelta: 1 }, { id: 'sleep', text: 'Тағы 5 минут ұйықтаймын', nextId: 'late' }, ], }, school: { id: 'school', title: '2-көрініс: Жолға дайындық', text: 'Жолда досыңды көрдің. Ол да AER019 туралы білгісі келеді.', choices: [ { id: 'friend', text: 'Бірге барып, балалар бұрышына кіреміз', nextId: 'classFun', starsDelta: 1 }, { id: 'rush', text: 'Мен асығып тұрмын, кейін айтамын', nextId: 'classSolo' }, ], }, late: { id: 'late', title: '2-көрініс: Кешігу', text: 'Сен гаражға кешігіп келдің. Механик сені күтіп тұр.', choices: [ { id: 'apologize', text: 'Кешіккенім үшін кешірім сұраймын', nextId: 'classSolo', starsDelta: 1, }, { id: 'silent', text: 'Ештеңе демеймін', nextId: 'endingOk' }, ], }, classFun: { id: 'classFun', title: '3-көрініс: Бірге үйрену', text: 'Сен досыңмен бірге «қос шелек әдісі» мен қауіпсіздік ережелерін үйрендің.', choices: [ { id: 'endGood', text: 'Күнім өте керемет өтті! 🎉', nextId: 'endingGood', starsDelta: 1 }, ], }, classSolo: { id: 'classSolo', title: '3-көрініс: Жауапкершілік', text: 'Сен нұсқауларды тыңдап, барлық тапсырманы мұқият орындайсың.', choices: [ { id: 'endCalm', text: 'Бүгін жақсы еңбек еттім деп ойлаймын 😊', nextId: 'endingGood', starsDelta: 1, }, ], }, endingGood: { id: 'endingGood', title: 'Қорытынды: Керемет күн', text: 'Сен дұрыс таңдаулар жасап, пайдалы күн өткіздің. AER019 Бот сенімен мақтанады!', choices: [], isEnd: true, }, endingOk: { id: 'endingOk', title: 'Қорытынды: Ойланатын күн', text: 'Бүгін бәрі онша болмады, бірақ келесі жолы жақсырақ істеуге болады!', choices: [], isEnd: true, }, }; // ----------------------- // Көмекші компоненттер // ----------------------- const PrimaryButton = ({ title, onPress }) => { const { primaryColor } = useContext(ThemeContext); const scale = useRef(new Animated.Value(1)).current; const handlePressIn = () => { Animated.spring(scale, { toValue: 0.93, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; const handlePressOut = () => { Animated.spring(scale, { toValue: 1, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; return ( <AnimatedTouchableOpacity style={[ styles.primaryButton, { backgroundColor: primaryColor, transform: [{ scale }], }, ]} onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut} activeOpacity={0.9} > <Text style={styles.primaryButtonText}>{title}</Text> </AnimatedTouchableOpacity> ); }; const SecondaryButton = ({ title, onPress, light }) => { const scale = useRef(new Animated.Value(1)).current; const handlePressIn = () => { Animated.spring(scale, { toValue: 0.95, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; const handlePressOut = () => { Animated.spring(scale, { toValue: 1, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; return ( <AnimatedTouchableOpacity style={[ styles.secondaryButton, light && styles.secondaryButtonLight, { transform: [{ scale }] }, ]} onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut} activeOpacity={0.9} > <Text style={[styles.secondaryButtonText, light && styles.secondaryButtonTextDark]}> {title} </Text> </AnimatedTouchableOpacity> ); }; const Card = ({ children }) => { const scale = useRef(new Animated.Value(0.96)).current; const opacity = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.parallel([ Animated.timing(scale, { toValue: 1, duration: 300, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true, }), ]).start(); }, [opacity, scale]); return ( <Animated.View style={[styles.card, { transform: [{ scale }], opacity }]}> {children} </Animated.View> ); }; const Screen = ({ colors, screenKey, children }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const translateY = useRef(new Animated.Value(24)).current; const { getScreenColors } = useContext(ThemeContext); const gradientColors = colors || getScreenColors(screenKey || 'default'); useEffect(() => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 450, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(translateY, { toValue: 0, duration: 450, easing: Easing.out(Easing.quad), useNativeDriver: true, }), ]).start(); }, [fadeAnim, translateY]); return ( <LinearGradient colors={gradientColors} style={styles.gradient}> <Animated.View style={{ flex: 1, opacity: fadeAnim, transform: [{ translateY }], }} > <ScrollView contentContainerStyle={styles.scroll}>{children}</ScrollView> </Animated.View> </LinearGradient> ); }; const FadeInView = ({ children, style, duration = 600, delay = 0 }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const translateY = useRef(new Animated.Value(16)).current; useEffect(() => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration, delay, useNativeDriver: true, }), Animated.timing(translateY, { toValue: 0, duration, delay, easing: Easing.out(Easing.quad), useNativeDriver: true, }), ]).start(); }, [delay, duration, fadeAnim, translateY]); return ( <Animated.View style={[ style, { opacity: fadeAnim, transform: [{ translateY }], }, ]} > {children} </Animated.View> ); }; const BouncyCharacters = ({ small }) => { const bounce = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.loop( Animated.sequence([ Animated.timing(bounce, { toValue: -10, duration: 600, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(bounce, { toValue: 0, duration: 600, easing: Easing.in(Easing.quad), useNativeDriver: true, }), ]), ).start(); }, [bounce]); return ( <View style={styles.charactersRow}> {CHARACTERS.map((ch) => ( <Animated.View key={ch.id} style={[ styles.characterBubble, small && styles.characterBubbleSmall, { backgroundColor: ch.color, transform: [{ translateY: bounce }], }, ]} > <Text style={styles.characterEmoji}>{ch.emoji}</Text> <Text style={styles.characterLabel}>{ch.label}</Text> </Animated.View> ))} </View> ); }; const WelcomeCarousel = () => { const scrollX = useRef(new Animated.Value(0)).current; const { width } = useWindowDimensions(); return ( <View style={styles.carouselContainer}> <Animated.ScrollView horizontal pagingEnabled showsHorizontalScrollIndicator={false} style={{ flexGrow: 0 }} onScroll={Animated.event( [{ nativeEvent: { contentOffset: { x: scrollX } } }], { useNativeDriver: false }, )} scrollEventThrottle={16} > {WELCOME_SLIDES.map((slide) => ( <View key={slide.key} style={{ width }}> <LinearGradient colors={slide.background} style={styles.carouselCard}> <Text style={styles.carouselEmoji}>{slide.emoji}</Text> <Text style={styles.carouselTitle}>{slide.title}</Text> <Text style={styles.carouselText}>{slide.text}</Text> </LinearGradient> </View> ))} </Animated.ScrollView> <View style={styles.carouselDots}> {WELCOME_SLIDES.map((_, index) => { const inputRange = [ (index - 1) * width, index * width, (index + 1) * width, ]; const dotWidth = scrollX.interpolate({ inputRange, outputRange: [8, 20, 8], extrapolate: 'clamp', }); return <Animated.View key={index} style={[styles.carouselDot, { width: dotWidth }]} />; })} </View> </View> ); }; const LevelProgressBar = ({ stars }) => { const { levelColor } = useContext(ThemeContext); const progressAnim = useRef(new Animated.Value(0)).current; useEffect(() => { const starsInLevel = stars % 5; const progress = starsInLevel / 5; Animated.timing(progressAnim, { toValue: progress, duration: 500, easing: Easing.out(Easing.quad), useNativeDriver: false, }).start(); }, [progressAnim, stars]); const widthInterpolated = progressAnim.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%'], }); return ( <View style={styles.levelBarOuter}> <Animated.View style={[ styles.levelBarInner, { width: widthInterpolated, backgroundColor: levelColor }, ]} /> </View> ); }; // Тыныс алу шеңбері const BreathingCircle = ({ playing }) => { const scale = useRef(new Animated.Value(1)).current; useEffect(() => { let animation; if (playing) { animation = Animated.loop( Animated.sequence([ Animated.timing(scale, { toValue: 1.25, duration: 4000, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }), Animated.timing(scale, { toValue: 0.9, duration: 4000, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }), ]), ); animation.start(); } else { scale.setValue(1); } return () => { if (animation) { animation.stop(); } }; }, [playing, scale]); return ( <View style={styles.breathingWrapper}> <Animated.View style={[styles.breathingCircle, { transform: [{ scale }] }]} /> </View> ); }; const speakKk = (text) => { Speech.stop(); Speech.speak(text, { language: 'kk-KZ', rate: 0.95, pitch: 1.0, }); }; // ----------------------- // Негізгі қосымша // ----------------------- export default function App() { // --------- Auth / User ---------- const [authMode, setAuthMode] = useState('login'); // 'login' | 'register' const [authStep, setAuthStep] = useState('credentials'); // 'credentials' | '2fa' | '3fa' const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [childName, setChildName] = useState(''); const [secretWord, setSecretWord] = useState(''); const [secretWordLogin, setSecretWordLogin] = useState(''); const [twoFaCode, setTwoFaCode] = useState(''); const [generatedCode, setGeneratedCode] = useState(''); const [savedUser, setSavedUser] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); // --------- Навигация ---------- const [currentScreen, setCurrentScreen] = useState('home'); // --------- Прогресс ---------- const [progress, setProgress] = useState({ gamesPlayed: 0, testsPassed: 0, stars: 0, }); // --------- Баптаулар ---------- const [settings, setSettings] = useState({ mathLevel: 'easy', // 'easy' | 'medium' | 'hard' voiceHints: true, chosenCharacterId: 'robot', }); // --------- Күнделікті миссиялар ---------- const [missions, setMissions] = useState({ date: '', items: [], rewardCollected: false, }); // --------- Математикалық ойын ---------- const [gameA, setGameA] = useState(0); const [gameB, setGameB] = useState(0); const [gameOp, setGameOp] = useState('+'); const [gameOptions, setGameOptions] = useState([]); const [gameMessage, setGameMessage] = useState(''); // --------- Тест ---------- const [quizIndex, setQuizIndex] = useState(0); const [quizCorrect, setQuizCorrect] = useState(0); const [quizFinished, setQuizFinished] = useState(false); // --------- Камера / AI ---------- const cameraRef = useRef(null); const [cameraPermission, requestCameraPermission] = useCameraPermissions(); const [cameraOpen, setCameraOpen] = useState(false); const [aiResult, setAiResult] = useState(''); const [aiLoading, setAiLoading] = useState(false); // --------- Жануарлар ойыны ---------- const [animalQuestion, setAnimalQuestion] = useState(ANIMAL_QUIZ[0]); const [animalOptions, setAnimalOptions] = useState([]); const [animalMessage, setAnimalMessage] = useState(''); // --------- Көңіл-күй ---------- const [moodToday, setMoodToday] = useState(null); // mood id const [moodMessage, setMoodMessage] = useState(''); // --------- Тыныс алу ---------- const [breathingPlaying, setBreathingPlaying] = useState(false); const [breathingLabel, setBreathingLabel] = useState('Дайынсың ба?'); const [breathingMessage, setBreathingMessage] = useState(''); // --------- Хикая ---------- const [storyNodeId, setStoryNodeId] = useState('start'); const [storyEnded, setStoryEnded] = useState(false); // --------- Тема (персонажға байланысты) ---------- const currentThemeKey = settings.chosenCharacterId || 'robot'; const baseTheme = THEMES[currentThemeKey] || THEMES.robot; const themeValue = { primaryColor: baseTheme.primaryColor, levelColor: baseTheme.levelColor, getScreenColors: (screenKey) => { const gradients = baseTheme.screenGradients || {}; if (gradients[screenKey]) return gradients[screenKey]; if (gradients.default) return gradients.default; return ['#020617', '#050816', '#0B1120']; }, }; // --------- Көмекші функциялар ---------- const getTodayKey = () => { const d = new Date(); const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${d.getFullYear()}-${mm}-${dd}`; }; const createDefaultMissions = () => [ { id: 'playGame', title: '1 математикалық ойын ойна', done: false }, { id: 'doQuiz', title: '1 тесттен өт', done: false }, { id: 'readText', title: 'Мәтінді дауыспен тыңда', done: false }, { id: 'chooseMood', title: 'Көңіл-күйіңді таңда', done: false }, { id: 'calmSession', title: '1 тыныс алу жаттығуын орында', done: false }, { id: 'storyPlay', title: 'Таңдау хикаясын бір рет ойна', done: false }, ]; const updateMissions = (updater) => { setMissions((prev) => { const nextState = typeof updater === 'function' ? updater(prev) : updater; AsyncStorage.setItem('aiKidsMissions', JSON.stringify(nextState)).catch((e) => { console.log('Save missions error', e); }); return nextState; }); }; const completeMission = (id) => { updateMissions((prev) => { if (!prev.items || prev.items.length === 0) return prev; const items = prev.items.map((m) => (m.id === id ? { ...m, done: true } : m)); return { ...prev, items }; }); }; const updateSettings = (patch) => { setSettings((prev) => { const updated = { ...prev, ...patch }; AsyncStorage.setItem('aiKidsSettings', JSON.stringify(updated)).catch((e) => console.log('Save settings error', e), ); return updated; }); }; const speakIfEnabled = (text) => { if (!settings.voiceHints) return; speakKk(text); }; // ----------------------- // Бастапқы жүктеу (SQLite + AsyncStorage) // ----------------------- useEffect(() => { if (db) { db.transaction((tx) => { tx.executeSql( `CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY NOT NULL, email TEXT, password TEXT, childName TEXT, secretWord TEXT );`, ); tx.executeSql( `CREATE TABLE IF NOT EXISTS progress ( id INTEGER PRIMARY KEY NOT NULL, gamesPlayed INTEGER, testsPassed INTEGER, stars INTEGER );`, ); }); } (async () => { try { if (db) { // USER db.transaction((tx) => { tx.executeSql( 'SELECT email, password, childName, secretWord FROM user LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const user = rows._array[0]; setSavedUser(user); await AsyncStorage.setItem('aiKidsUser', JSON.stringify(user)); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) { setSavedUser(JSON.parse(jsonUser)); } } }, (_, error) => { console.log('User select error', error); return false; }, ); }); // PROGRESS db.transaction((tx) => { tx.executeSql( 'SELECT gamesPlayed, testsPassed, stars FROM progress LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const p = rows._array[0]; const loaded = { gamesPlayed: p.gamesPlayed ?? 0, testsPassed: p.testsPassed ?? 0, stars: p.stars ?? 0, }; setProgress(loaded); await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(loaded)); } else { const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) { setProgress(JSON.parse(jsonProgress)); } } }, (_, error) => { console.log('Progress select error', error); return false; }, ); }); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) setSavedUser(JSON.parse(jsonUser)); const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) setProgress(JSON.parse(jsonProgress)); } // SETTINGS const jsonSettings = await AsyncStorage.getItem('aiKidsSettings'); if (jsonSettings) { setSettings(JSON.parse(jsonSettings)); } const today = getTodayKey(); // MISSIONS const jsonMissions = await AsyncStorage.getItem('aiKidsMissions'); if (jsonMissions) { try { const saved = JSON.parse(jsonMissions); if (saved.date === today) { setMissions(saved); } else { const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } catch (err) { console.log('Parse missions error', err); const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } else { const fresh = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } // MOOD const jsonMood = await AsyncStorage.getItem('aiKidsMood'); if (jsonMood) { try { const savedMood = JSON.parse(jsonMood); if (savedMood.date === today) { setMoodToday(savedMood.moodId); } } catch (e) { console.log('Parse mood error', e); } } } catch (e) { console.log('Load storage / SQL error', e); } })(); }, []); // ----------------------- // Прогресс // ----------------------- const addProgress = async ({ gamesDelta = 0, testsDelta = 0, starsDelta = 0 }) => { try { const updated = { gamesPlayed: progress.gamesPlayed + gamesDelta, testsPassed: progress.testsPassed + testsDelta, stars: progress.stars + starsDelta, }; setProgress(updated); await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(updated)); if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM progress;'); tx.executeSql( 'INSERT INTO progress (gamesPlayed, testsPassed, stars) VALUES (?, ?, ?);', [updated.gamesPlayed, updated.testsPassed, updated.stars], () => console.log('Progress inserted into SQLite'), (_, error) => { console.log('Progress insert error', error); return false; }, ); }); } } catch (e) { console.log('Save progress error', e); } }; const collectMissionReward = () => { setMissions((prev) => { if (!prev.items || prev.items.length === 0) { Alert.alert('Миссиялар', 'Бүгінгі миссиялар табылмады.'); return prev; } const allDone = prev.items.every((m) => m.done); if (!allDone) { Alert.alert('Миссиялар', 'Алдымен барлық миссияны орындап шық.'); return prev; } if (prev.rewardCollected) { Alert.alert('Миссиялар', 'Бүгінгі сыйлықты бұрын алғансың.'); return prev; } addProgress({ starsDelta: 5 }); speakIfEnabled('Керемет! Күнделікті миссиялар үшін қосымша 5 жұлдыз алдың!'); const updated = { ...prev, rewardCollected: true }; AsyncStorage.setItem('aiKidsMissions', JSON.stringify(updated)).catch((err) => console.log('Save missions reward error', err), ); return updated; }); }; // ----------------------- // Тыныс алу useEffect // ----------------------- useEffect(() => { let intervalId; let timeoutId; if (breathingPlaying) { let phase = 0; setBreathingLabel('Дем ал'); setBreathingMessage(''); intervalId = setInterval(() => { phase = 1 - phase; setBreathingLabel(phase === 0 ? 'Дем ал' : 'Дем шығар'); }, 4000); timeoutId = setTimeout(() => { setBreathingPlaying(false); const msg = 'Керемет! Тыныс алу жаттығуын аяқтадың, 1 жұлдыз сыйлаймын.'; setBreathingMessage(msg); speakIfEnabled(msg); addProgress({ starsDelta: 1 }); completeMission('calmSession'); }, 24000); } else { setBreathingLabel('Дайынсың ба?'); } return () => { if (intervalId) clearInterval(intervalId); if (timeoutId) clearTimeout(timeoutId); }; }, [breathingPlaying]); // ----------------------- // Auth логикасы // ----------------------- const handleRegister = async () => { if (!email || !password || !childName || !secretWord) { Alert.alert('Қате', 'Барлық өрістерді толтырыңыз.'); return; } try { const newUser = { email, password, childName, secretWord }; await AsyncStorage.setItem('aiKidsUser', JSON.stringify(newUser)); setSavedUser(newUser); if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM user;'); tx.executeSql( 'INSERT INTO user (email, password, childName, secretWord) VALUES (?, ?, ?, ?);', [newUser.email, newUser.password, newUser.childName, newUser.secretWord], () => console.log('User inserted into SQLite'), (_, error) => { console.log('User insert error', error); return false; }, ); }); } Alert.alert('Тіркелу', 'Аккаунт сәтті жасалды! Енді кіруге болады.'); setAuthMode('login'); setAuthStep('credentials'); setPassword(''); setSecretWord(''); } catch (e) { Alert.alert('Қате', 'Пайдаланушыны сақтау кезінде қате кетті.'); } }; const handleLoginCredentials = () => { if (!savedUser) { Alert.alert('Ақпарат', 'Алдымен тіркеліп алыңыз.'); return; } if (email === savedUser.email && password === savedUser.password) { const code = Math.floor(100000 + Math.random() * 900000).toString(); setGeneratedCode(code); setAuthStep('2fa'); setTwoFaCode(''); Alert.alert( '2FA коды', `Демо режим: әдетте бұл код SMS немесе e-mail арқылы келеді.\nҚазір тек тест үшін код: ${code}`, ); } else { Alert.alert('Қате', 'E-mail немесе құпия сөз дұрыс емес.'); } }; const handleCheck2FA = () => { if (twoFaCode === generatedCode) { setAuthStep('3fa'); } else { Alert.alert('Қате', '2FA коды дұрыс емес.'); } }; const finishLogin = () => { setIsLoggedIn(true); setCurrentScreen('home'); setAuthStep('credentials'); setSecretWordLogin(''); setTwoFaCode(''); }; const handleThreeFA = async () => { try { const hasHardware = await LocalAuthentication.hasHardwareAsync(); if (hasHardware) { const result = await LocalAuthentication.authenticateAsync({ promptMessage: 'AER019 Kids App - 3FA тексеру', fallbackLabel: 'Қауіпсіздік сөзімен растау', }); if (result.success) { finishLogin(); return; } } if (savedUser && secretWordLogin === savedUser.secretWord) { finishLogin(); } else { Alert.alert('3FA', 'Қауіпсіздік сөзі дұрыс емес.'); } } catch (e) { Alert.alert('Қате', '3FA тексеру кезінде қате кетті.'); } }; const handleLogout = () => { setIsLoggedIn(false); setCurrentScreen('home'); setEmail(''); setPassword(''); setAuthStep('credentials'); }; // ----------------------- // Навигация және ойындарды дайындау // ----------------------- const setupMathGame = () => { const createNumber = () => { if (settings.mathLevel === 'easy') { return Math.floor(Math.random() * 10) + 1; } if (settings.mathLevel === 'medium') { return Math.floor(Math.random() * 16) + 5; // 5-20 } return Math.floor(Math.random() * 41) + 10; // 10-50 }; let a = createNumber(); let b = createNumber(); let op = '+'; if (settings.mathLevel === 'easy') { op = '+'; } else { op = Math.random() < 0.5 ? '+' : '-'; } let correct; if (op === '-') { if (a < b) [a, b] = [b, a]; correct = a - b; } else { correct = a + b; } const options = new Set([correct]); const variation = settings.mathLevel === 'hard' ? 10 : 5; while (options.size < 4) { const fake = correct + (Math.floor(Math.random() * (variation * 2 + 1)) - variation); if (fake > 0 && fake !== correct) options.add(fake); } const optionsArray = Array.from(options); optionsArray.sort(() => Math.random() - 0.5); setGameA(a); setGameB(b); setGameOp(op); setGameOptions(optionsArray); setGameMessage(''); }; const setupAnimalRound = () => { const correct = ANIMAL_QUIZ[Math.floor(Math.random() * ANIMAL_QUIZ.length)]; const shuffled = [...ANIMAL_QUIZ].sort(() => Math.random() - 0.5); const options = [correct]; for (const item of shuffled) { if (options.length >= 3) break; if (item.id !== correct.id) options.push(item); } options.sort(() => Math.random() - 0.5); setAnimalQuestion(correct); setAnimalOptions(options); setAnimalMessage(''); }; const resetQuiz = () => { setQuizIndex(0); setQuizCorrect(0); setQuizFinished(false); }; const resetStory = () => { setStoryNodeId('start'); setStoryEnded(false); }; const goToScreen = (screenName) => { if (screenName === 'games') { setupMathGame(); } if (screenName === 'quiz') { resetQuiz(); } if (screenName === 'animals') { setupAnimalRound(); } if (screenName === 'story') { resetStory(); } setCurrentScreen(screenName); }; // ----------------------- // Дауыспен оқу // ----------------------- const handleSpeakReading = () => { speakIfEnabled(READING_TEXT); completeMission('readText'); }; const handleStopSpeak = () => { Speech.stop(); }; // ----------------------- // Математикалық ойын // ----------------------- const handleGameAnswer = (value) => { const correct = gameOp === '-' ? gameA - gameB : gameA + gameB; if (value === correct) { const msg = 'Керемет! Дұрыс жауап! ⭐'; setGameMessage(msg); speakIfEnabled(msg); addProgress({ gamesDelta: 1, starsDelta: 1 }); completeMission('playGame'); setTimeout(setupMathGame, 700); } else { const msg = 'Әлі де тырысып көр! Қолыңнан келеді!'; setGameMessage(msg); speakIfEnabled(msg); } }; // ----------------------- // Тест // ----------------------- const handleQuizAnswer = (index) => { const currentQ = QUIZ_DATA[quizIndex]; if (index === currentQ.correctIndex) { setQuizCorrect((prev) => prev + 1); } if (quizIndex + 1 < QUIZ_DATA.length) { setQuizIndex((prev) => prev + 1); } else { setQuizFinished(true); addProgress({ testsDelta: 1, starsDelta: QUIZ_DATA.length, }); completeMission('doQuiz'); } }; // ----------------------- // Камера + AI "сурет тану" // ----------------------- const openCamera = async () => { try { let permission = cameraPermission; if (!permission || !permission.granted) { const result = await requestCameraPermission(); permission = result; } if (!permission || !permission.granted) { Alert.alert('Рұқсат жоқ', 'Камераға рұқсат беру қажет.'); return; } setCameraOpen(true); setAiResult(''); } catch (e) { Alert.alert('Қате', 'Камера рұқсатын сұрау кезінде қате кетті.'); } }; const handleTakePicture = async () => { if (!cameraRef.current) return; try { setAiLoading(true); const photo = await cameraRef.current.takePictureAsync({ base64: true, quality: 0.3, }); // Демо режим – кездейсоқ "AI болжамы" const labels = ['көлік', 'доп', 'шелек', 'шина', 'кітап', 'адам']; const randomLabel = labels[Math.floor(Math.random() * labels.length)]; const text = `AI болжамы: ${randomLabel}. Бұл тек демо, нақты модельмен алмастыруға болады.`; console.log('demo photo base64 length:', photo?.base64?.length || 'no-base64'); setAiResult(text); speakIfEnabled(text); } catch (e) { Alert.alert('Қате', 'Сурет түсіру кезінде қате кетті.'); } finally { setAiLoading(false); setCameraOpen(false); } }; // ----------------------- // Жануарлар ойыны // ----------------------- const handleAnimalAnswer = (animalId) => { if (!animalQuestion) return; if (animalId === animalQuestion.id) { const text = `Дұрыс! Бұл ${animalQuestion.name}.`; setAnimalMessage(text + ' ⭐'); speakIfEnabled(text); addProgress({ gamesDelta: 1, starsDelta: 1 }); setTimeout(setupAnimalRound, 800); } else { const text = 'Сәл қателестік. Қайта байқап көрейік!'; setAnimalMessage(text); speakIfEnabled(text); } }; // ----------------------- // Көңіл-күй логикасы // ----------------------- const handleMoodSelect = (option) => { const today = getTodayKey(); if (moodToday === option.id) { const text = `${option.emoji} Көңіл-күйің қазірдің өзінде "${option.label}".`; setMoodMessage(text); speakIfEnabled(text); } else { setMoodToday(option.id); const text = `${option.emoji} Бүгінгі көңіл-күйің: ${option.label}. Жарайсың!`; setMoodMessage(text + ' ⭐'); speakIfEnabled(text); addProgress({ starsDelta: 1 }); completeMission('chooseMood'); AsyncStorage.setItem( 'aiKidsMood', JSON.stringify({ date: today, moodId: option.id }), ).catch((err) => console.log('Save mood error', err)); } }; // ----------------------- // Хикая логикасы // ----------------------- const handleStoryChoice = (choice) => { if (choice.starsDelta) { addProgress({ starsDelta: choice.starsDelta }); } if (choice.nextId && STORY_NODES[choice.nextId]) { const nextNode = STORY_NODES[choice.nextId]; setStoryNodeId(choice.nextId); setStoryEnded(!!nextNode.isEnd); if (nextNode.isEnd) { completeMission('storyPlay'); } } else { setStoryEnded(true); completeMission('storyPlay'); } }; const toggleBreathing = () => { setBreathingPlaying((prev) => !prev); }; // ----------------------- // Экрандар // ----------------------- const renderAuth = () => ( <Screen screenKey="auth"> <FadeInView> <Text style={styles.appTitle}>AER019 Kids Garage</Text> <Text style={styles.appSubtitle}> Балаларға арналған ойын-форматтағы AER019 Detailing әлемі </Text> </FadeInView> <FadeInView delay={80}> <BouncyCharacters /> </FadeInView> <FadeInView delay={160}> <WelcomeCarousel /> </FadeInView> <View style={styles.authSwitchRow}> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'login' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('login'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'login' && styles.authSwitchTextActive, ]} > Кіру </Text> </TouchableOpacity> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'register' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('register'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'register' && styles.authSwitchTextActive, ]} > Тіркелу </Text> </TouchableOpacity> </View> <Card> {authMode === 'register' && ( <> <Text style={styles.sectionTitle}>Тіркелу</Text> <TextInput placeholder="Бала аты" style={styles.input} value={childName} onChangeText={setChildName} /> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <TextInput placeholder="Қауіпсіздік сөзі (3FA үшін)" style={styles.input} value={secretWord} onChangeText={setSecretWord} /> <PrimaryButton title="Тіркелу" onPress={handleRegister} /> <Text style={styles.helperText}> Қауіпсіздік сөзі – 3-ші фактор (3FA) ретінде қолданылады. </Text> </> )} {authMode === 'login' && ( <> {authStep === 'credentials' && ( <> <Text style={styles.sectionTitle}>Кіру (1-фактор)</Text> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <PrimaryButton title="Кіру" onPress={handleLoginCredentials} /> <Text style={styles.helperText}>1-фактор — логин және құпия сөз.</Text> </> )} {authStep === '2fa' && ( <> <Text style={styles.sectionTitle}>2FA – код енгізу</Text> <TextInput placeholder="SMS / E-mail код" style={styles.input} value={twoFaCode} onChangeText={setTwoFaCode} keyboardType="numeric" /> <PrimaryButton title="Кодты растау" onPress={handleCheck2FA} /> <Text style={styles.helperText}> 2-фактор — бір реттік код. Бұл демо режим: код Alert арқылы көрсетілді. </Text> </> )} {authStep === '3fa' && ( <> <Text style={styles.sectionTitle}>3FA – биометрия немесе құпия сөз</Text> <TextInput placeholder="Қауіпсіздік сөзі (егер биометрия жоқ болса)" style={styles.input} value={secretWordLogin} onChangeText={setSecretWordLogin} /> <PrimaryButton title="3FA тексеру" onPress={handleThreeFA} /> <Text style={styles.helperText}> 3-фактор — биометрия (Face ID / Fingerprint) немесе алдын ала сақталған қауіпсіздік сөзі. </Text> </> )} </> )} </Card> </Screen> ); const renderHome = () => { const currentLevel = Math.floor(progress.stars / 5) + 1; const starsInLevel = progress.stars % 5; const remaining = starsInLevel === 0 ? 5 : 5 - starsInLevel; const activeCharacter = CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0]; const today = getTodayKey(); return ( <Screen screenKey="home"> <View style={styles.headerRow}> <View style={{ flex: 1 }}> <Text style={styles.homeTitle}> Сәлем, {savedUser?.childName || 'досым'}! 👋 </Text> <Text style={styles.homeSubtitleSmall}> Жұлдыздар: {progress.stars} | Деңгей: {currentLevel} </Text> <Text style={styles.homeSubtitleSmall}> Бүгін: {today} | Сенің AER019 досың: {activeCharacter.emoji} {activeCharacter.label} </Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.homeSubtitleSmall}> Келесі деңгейге дейін: {remaining} жұлдыз </Text> </View> <SecondaryButton title="Шығу" onPress={handleLogout} /> </View> <BouncyCharacters small /> <Text style={styles.appSubtitle}> AER019 Бот пен достары сені гаражда күтіп тұр! Қай модульге барамыз? 🌌 </Text> <Card> <Text style={styles.sectionTitle}>📚 Оқу мен білім</Text> <PrimaryButton title="🎧 Дауыспен оқу" onPress={() => goToScreen('reading')} /> <PrimaryButton title="🎮 Математикалық ойын" onPress={() => goToScreen('games')} /> <PrimaryButton title="📝 Тест" onPress={() => goToScreen('quiz')} /> <PrimaryButton title="🔤 Қазақ әліпбиі" onPress={() => goToScreen('alphabet')} /> <PrimaryButton title="🎨 Түстер" onPress={() => goToScreen('colors')} /> </Card> <Card> <Text style={styles.sectionTitle}>🎡 Қызық аймақ</Text> <PrimaryButton title="🎭 Көңіл-күй күнделігі" onPress={() => goToScreen('mood')} /> <PrimaryButton title="🌬️ Тыныс алу ойыны" onPress={() => goToScreen('calm')} /> <PrimaryButton title="📚 Таңдау хикаясы" onPress={() => goToScreen('story')} /> <PrimaryButton title="🐾 Жануарларды таны" onPress={() => goToScreen('animals')} /> <PrimaryButton title="📷 Сурет тану (AI)" onPress={() => goToScreen('image')} /> </Card> <Card> <Text style={styles.sectionTitle}>⭐ Прогресс және миссиялар</Text> <Text style={styles.progressText}>Ойындар: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жиналған жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}>Деңгей: {currentLevel}-деңгей 🌈</Text> <View style={{ height: 8 }} /> <PrimaryButton title="🎯 Күнделікті миссиялар" onPress={() => goToScreen('missions')} /> <PrimaryButton title="🏆 Жетістіктер" onPress={() => goToScreen('achievements')} /> <PrimaryButton title="⚙️ Баптаулар" onPress={() => goToScreen('settings')} /> </Card> </Screen> ); }; const renderReading = () => ( <Screen screenKey="reading"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎧 Дауыспен оқу</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>AER019 туралы мәтін</Text> <Text style={styles.readingText}>{READING_TEXT}</Text> <PrimaryButton title="Дауыспен оқу" onPress={handleSpeakReading} /> <SecondaryButton title="Тоқтату" onPress={handleStopSpeak} light /> <Text style={styles.helperText}> Бұл жерде AI-дың дауыс синтезі (Text-to-Speech) қолданылады. </Text> </Card> </Screen> ); const renderGames = () => ( <Screen screenKey="games"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎮 Математикалық ойын</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Есептер – гараждағы логика</Text> <Text style={styles.bigQuestion}> {gameA} {gameOp} {gameB} = ? </Text> <View style={styles.optionsRow}> {gameOptions.map((opt) => ( <TouchableOpacity key={opt} style={styles.optionButton} onPress={() => handleGameAnswer(opt)} > <Text style={styles.optionText}>{opt}</Text> </TouchableOpacity> ))} </View> {gameMessage !== '' && <Text style={styles.gameMessage}>{gameMessage}</Text>} </Card> </Screen> ); const renderQuiz = () => { const currentQ = QUIZ_DATA[quizIndex]; return ( <Screen screenKey="quiz"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📝 Тест (детейлинг)</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> {!quizFinished ? ( <> <Text style={styles.sectionTitle}> Сұрақ {quizIndex + 1} / {QUIZ_DATA.length} </Text> <Text style={styles.quizQuestion}>{currentQ.text}</Text> {currentQ.options.map((opt, idx) => ( <TouchableOpacity key={idx} style={styles.optionButtonWide} onPress={() => handleQuizAnswer(idx)} > <Text style={styles.optionText}>{opt}</Text> </TouchableOpacity> ))} </> ) : ( <> <Text style={styles.sectionTitle}>Нәтиже</Text> <Text style={styles.quizResultText}> Дұрыс жауаптар: {quizCorrect} / {QUIZ_DATA.length} </Text> <Text style={styles.helperText}> Жарайсың! Сенің жұлдыздарың прогресске қосылды. </Text> <PrimaryButton title="Қайта тапсыру" onPress={resetQuiz} /> </> )} </Card> </Screen> ); }; const renderProgress = () => { const currentLevel = Math.floor(progress.stars / 5) + 1; return ( <Screen screenKey="progress"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⭐ Прогресс</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> <Text style={styles.progressText}>Ойындар саны: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер саны: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}> Деңгей: {currentLevel}-деңгей 🌟 </Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.helperText}> Барлық деректер құрылғыда сақталады. Көп жеңіс – көп деңгей! </Text> </Card> </Screen> ); }; const renderImageAI = () => ( <Screen screenKey="image"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📷 AI – сурет тану</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Камера арқылы объект тану</Text> <Text style={styles.helperText}> Камераны ашып, бір затты түсір. AER019 Бот сенімен бірге не екенін болжайды (демо режим, шынайы модельмен ауыстыруға болады). </Text> <PrimaryButton title="Камераны ашу" onPress={openCamera} /> {aiLoading && ( <View style={styles.loadingRow}> <ActivityIndicator size="large" /> <Text style={styles.helperText}>AI суретті талдап жатыр...</Text> </View> )} {aiResult !== '' && <Text style={styles.aiResultText}>{aiResult}</Text>} </Card> {cameraOpen && ( <Card> <Text style={styles.sectionTitle}>Камера</Text> <View style={styles.cameraBox}> <CameraView style={styles.camera} ref={cameraRef} facing="back" /> </View> <PrimaryButton title="Суретке түсіру" onPress={handleTakePicture} /> </Card> )} </Screen> ); const renderAnimals = () => ( <Screen screenKey="animals"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🐾 Жануарларды таны</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Бұл қандай жануар?</Text> <View style={styles.animalEmojiBox}> <Text style={styles.animalEmoji}>{animalQuestion?.emoji}</Text> </View> <Text style={styles.helperText}>Төмендегі сөздердің ішінен дұрыс жануарды таңда.</Text> <View style={styles.optionsRow}> {animalOptions.map((animal) => ( <TouchableOpacity key={animal.id} style={styles.optionButton} onPress={() => handleAnimalAnswer(animal.id)} > <Text style={styles.optionText}>{animal.name}</Text> </TouchableOpacity> ))} </View> {animalMessage !== '' && <Text style={styles.gameMessage}>{animalMessage}</Text>} </Card> </Screen> ); const renderAlphabet = () => ( <Screen screenKey="alphabet"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🔤 Қазақ әліпбиі (гараж алфавиті)</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Әріпті бас – дауыстап айтсын</Text> <Text style={styles.helperText}> Әр түймені басқанда, әріп пен мысал сөз қазақ тілінде дауыстап айтылады. </Text> <View style={styles.alphabetGrid}> {KAZAKH_ALPHABET.map((item) => ( <TouchableOpacity key={item.letter} style={styles.alphabetButton} onPress={() => speakIfEnabled(`${item.letter}${item.word}`)} > <Text style={styles.alphabetLetter}>{item.letter}</Text> <Text style={styles.alphabetWord}>{item.word}</Text> </TouchableOpacity> ))} </View> </Card> </Screen> ); const renderColors = () => ( <Screen screenKey="colors"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎨 Түстер және авто</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Түстерді үйренейік</Text> <Text style={styles.helperText}> Әр түсті басқанда, оның атауы мен қысқа сипаттамасы дауыстап айтылады. </Text> <View style={styles.colorsGrid}> {COLOR_LESSONS.map((color) => ( <TouchableOpacity key={color.id} style={[styles.colorCard, { backgroundColor: color.bg }]} onPress={() => speakIfEnabled(`${color.name}. ${color.text}`)} > <Text style={styles.colorName}>{color.name}</Text> <Text style={styles.colorText}>{color.text}</Text> </TouchableOpacity> ))} </View> </Card> </Screen> ); const renderMissions = () => ( <Screen screenKey="missions"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎯 Күнделікті миссиялар</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}> Бүгінгі тапсырмалар ({missions.date || getTodayKey()}) </Text> {missions.items && missions.items.length > 0 ? ( missions.items.map((m) => ( <View key={m.id} style={styles.missionRow}> <Text style={styles.missionIcon}>{m.done ? '⭐' : '⬜️'}</Text> <Text style={[styles.missionText, m.done && styles.missionTextDone]}> {m.title} </Text> </View> )) ) : ( <Text style={styles.helperText}> Миссиялар жүктелуде немесе бүгінге арналған тапсырмалар жоқ. </Text> )} <PrimaryButton title="Сыйлықты алу" onPress={collectMissionReward} /> <Text style={styles.helperText}> Барлық миссияларды орындасаң, қосымша жұлдыздар беріледі. Миссиялар әр күн сайын жаңарады. </Text> </Card> </Screen> ); const renderAchievements = () => ( <Screen screenKey="achievements"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🏆 Жетістіктер</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> {ACHIEVEMENTS.map((ach) => { const value = ach.type === 'gamesPlayed' ? progress.gamesPlayed : ach.type === 'testsPassed' ? progress.testsPassed : progress.stars; const unlocked = value >= ach.threshold; return ( <View key={ach.id} style={styles.achievementRow}> <Text style={styles.achievementIcon}>{unlocked ? '✅' : '🔒'}</Text> <View style={{ flex: 1 }}> <Text style={styles.achievementTitle}>{ach.title}</Text> <Text style={styles.achievementDesc}> {ach.description} ({Math.min(value, ach.threshold)}/{ach.threshold}) </Text> </View> </View> ); })} <Text style={styles.helperText}> Жұлдыздар, ойындар және тесттер саны көбейген сайын жаңа жетістіктер ашылады. </Text> </Card> </Screen> ); const renderSettings = () => { const activeCharacter = CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0]; return ( <Screen screenKey="settings"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⚙️ Баптаулар</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Дыбыс</Text> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Дауыстық көмек</Text> <Switch value={settings.voiceHints} onValueChange={(value) => updateSettings({ voiceHints: value })} /> </View> <Text style={styles.helperText}> Егер өшірсең, қолданба ештеңе сөйлемейді (мәтін де, кеңестер де). </Text> </Card> <Card> <Text style={styles.sectionTitle}>Математика деңгейі</Text> <View style={styles.difficultyRow}> {[ { id: 'easy', label: 'Жеңіл' }, { id: 'medium', label: 'Орта' }, { id: 'hard', label: 'Қиын' }, ].map((lvl) => ( <TouchableOpacity key={lvl.id} style={[ styles.difficultyButton, settings.mathLevel === lvl.id && styles.difficultyButtonActive, ]} onPress={() => updateSettings({ mathLevel: lvl.id })} > <Text style={[ styles.difficultyText, settings.mathLevel === lvl.id && styles.difficultyTextActive, ]} > {lvl.label} </Text> </TouchableOpacity> ))} </View> <Text style={styles.helperText}> Деңгей жоғарылаған сайын сандар да үлкенірек болады, кейде азайту амалы да қосылады. </Text> </Card> <Card> <Text style={styles.sectionTitle}>Кейіпкер таңдау</Text> <Text style={styles.helperText}> Таңдалған кейіпкер басты экранда аватар ретінде көрсетіледі, ал бүкіл қолданбаның түс схемасы соған сай өзгереді. </Text> <View style={styles.charactersRow}> {CHARACTERS.map((ch) => { const isActive = ch.id === settings.chosenCharacterId; return ( <TouchableOpacity key={ch.id} style={[styles.characterBubble, isActive && styles.characterBubbleActive]} onPress={() => updateSettings({ chosenCharacterId: ch.id })} > <Text style={styles.characterEmoji}>{ch.emoji}</Text> <Text style={styles.characterLabel}>{ch.label}</Text> </TouchableOpacity> ); })} </View> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Таңдалған кейіпкер</Text> <Text style={styles.settingsValue}> {activeCharacter.emoji} {activeCharacter.label} </Text> </View> </Card> </Screen> ); }; const renderMood = () => ( <Screen screenKey="mood"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎭 Көңіл-күй күнделігі</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Бүгін өзіңді қалай сезініп тұрсың?</Text> <Text style={styles.helperText}> Бір эмоцияны таңда. AER019 Бот сенің көңіл-күйіңді еске сақтайды. </Text> <View style={styles.moodGrid}> {MOOD_OPTIONS.map((m) => { const active = moodToday === m.id; return ( <TouchableOpacity key={m.id} style={[ styles.moodCard, { backgroundColor: m.color }, active && styles.moodCardActive, ]} onPress={() => handleMoodSelect(m)} > <Text style={styles.moodEmoji}>{m.emoji}</Text> <Text style={styles.moodLabel}>{m.label}</Text> </TouchableOpacity> ); })} </View> {moodMessage !== '' && <Text style={styles.gameMessage}>{moodMessage}</Text>} </Card> </Screen> ); const renderCalm = () => ( <Screen screenKey="calm"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🌬️ Тыныс алу ойыны</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Cпокой дем ал, дем шығар</Text> <Text style={styles.helperText}> Шеңбер үлкейгенде – дем ал, кішірейгенде – дем шығар. Бірнеше рет қайтала. </Text> <BreathingCircle playing={breathingPlaying} /> <Text style={styles.breathingLabel}>{breathingLabel}</Text> <PrimaryButton title={breathingPlaying ? 'Тоқтату' : 'Бастау'} onPress={toggleBreathing} /> {breathingMessage !== '' && ( <Text style={styles.gameMessage}>{breathingMessage}</Text> )} </Card> </Screen> ); const renderStory = () => { const node = STORY_NODES[storyNodeId] || STORY_NODES.start; return ( <Screen screenKey="story"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📚 Таңдау хикаясы</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>{node.title}</Text> <Text style={styles.readingText}>{node.text}</Text> {!node.isEnd && node.choices && node.choices.length > 0 && ( <> {node.choices.map((choice) => ( <TouchableOpacity key={choice.id} style={styles.optionButtonWide} onPress={() => handleStoryChoice(choice)} > <Text style={styles.optionText}>{choice.text}</Text> </TouchableOpacity> ))} </> )} {node.isEnd && ( <> <Text style={styles.helperText}> Хикая аяқталды. Таңдауларың үшін рахмет! Тағы да ойнағың келсе – басынан бастауға болады. </Text> <PrimaryButton title="Қайта бастау" onPress={resetStory} /> </> )} </Card> </Screen> ); }; // ----------------------- // Негізгі render // ----------------------- return ( <ThemeContext.Provider value={themeValue}> <SafeAreaView style={styles.safe}> {!isLoggedIn ? renderAuth() : currentScreen === 'home' ? renderHome() : currentScreen === 'reading' ? renderReading() : currentScreen === 'games' ? renderGames() : currentScreen === 'quiz' ? renderQuiz() : currentScreen === 'progress' ? renderProgress() : currentScreen === 'image' ? renderImageAI() : currentScreen === 'animals' ? renderAnimals() : currentScreen === 'alphabet' ? renderAlphabet() : currentScreen === 'colors' ? renderColors() : currentScreen === 'missions' ? renderMissions() : currentScreen === 'achievements' ? renderAchievements() : currentScreen === 'settings' ? renderSettings() : currentScreen === 'mood' ? renderMood() : currentScreen === 'calm' ? renderCalm() : currentScreen === 'story' ? renderStory() : renderHome()} </SafeAreaView> </ThemeContext.Provider> ); } // ----------------------- // Стильдер – AER019 неон-гараж стилі // ----------------------- const styles = StyleSheet.create({ safe: { flex: 1, backgroundColor: '#000', }, gradient: { flex: 1, }, scroll: { padding: 20, paddingBottom: 40, }, appTitle: { fontSize: 30, fontWeight: '800', color: '#fff', textAlign: 'center', marginBottom: 8, letterSpacing: 0.5, }, appSubtitle: { fontSize: 16, color: 'rgba(255,255,255,0.9)', textAlign: 'center', marginBottom: 16, }, homeTitle: { fontSize: 22, fontWeight: '700', color: '#fff', }, homeSubtitleSmall: { fontSize: 14, color: 'rgba(255,255,255,0.9)', marginTop: 4, }, headerRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, gap: 8, justifyContent: 'space-between', }, card: { backgroundColor: 'rgba(255,255,255,0.96)', borderRadius: 22, padding: 18, marginBottom: 16, shadowColor: '#000', shadowOpacity: 0.18, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 5, }, sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 10, color: '#222', }, primaryButton: { backgroundColor: '#00E0FF', paddingVertical: 13, borderRadius: 18, alignItems: 'center', marginTop: 8, }, primaryButtonText: { color: '#020617', fontSize: 15, fontWeight: '700', }, secondaryButton: { borderColor: 'rgba(255,255,255,0.9)', borderWidth: 1, paddingVertical: 8, paddingHorizontal: 12, borderRadius: 20, }, secondaryButtonLight: { borderColor: '#ccc', backgroundColor: '#fff', }, secondaryButtonText: { color: '#fff', fontSize: 14, fontWeight: '600', }, secondaryButtonTextDark: { color: '#333', }, input: { backgroundColor: '#0F172A', borderRadius: 14, paddingHorizontal: 14, paddingVertical: 9, marginBottom: 10, fontSize: 14, borderWidth: 1, borderColor: '#1E293B', color: '#E5E7EB', }, helperText: { fontSize: 12, color: '#777', marginTop: 8, }, authSwitchRow: { flexDirection: 'row', backgroundColor: 'rgba(15,23,42,0.7)', borderRadius: 30, padding: 4, marginBottom: 16, alignSelf: 'center', }, authSwitchButton: { flex: 1, paddingVertical: 8, borderRadius: 26, alignItems: 'center', }, authSwitchButtonActive: { backgroundColor: 'rgba(255,255,255,0.95)', }, authSwitchText: { color: 'rgba(255,255,255,0.9)', fontWeight: '600', }, authSwitchTextActive: { color: '#111827', }, readingText: { fontSize: 16, lineHeight: 22, color: '#444', marginBottom: 12, }, bigQuestion: { fontSize: 30, fontWeight: '800', textAlign: 'center', marginBottom: 12, color: '#111827', }, optionsRow: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8, }, optionButton: { backgroundColor: '#E0F2FE', paddingVertical: 10, paddingHorizontal: 14, borderRadius: 14, marginTop: 8, flexBasis: '48%', alignItems: 'center', }, optionButtonWide: { backgroundColor: '#E0F2FE', paddingVertical: 10, paddingHorizontal: 14, borderRadius: 14, marginTop: 8, }, optionText: { fontSize: 14, fontWeight: '600', color: '#0F172A', }, gameMessage: { marginTop: 12, fontSize: 15, textAlign: 'center', color: '#111827', }, quizQuestion: { fontSize: 16, marginBottom: 8, color: '#111827', }, quizResultText: { fontSize: 18, fontWeight: '700', marginBottom: 8, color: '#111827', }, progressText: { fontSize: 15, marginBottom: 4, color: '#111827', }, cameraBox: { borderRadius: 20, overflow: 'hidden', height: 220, marginBottom: 12, }, camera: { flex: 1, }, loadingRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 12, }, aiResultText: { marginTop: 10, fontSize: 15, fontWeight: '600', color: '#111827', }, animalEmojiBox: { alignItems: 'center', marginBottom: 12, }, animalEmoji: { fontSize: 86, }, alphabetGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8, marginTop: 12, }, alphabetButton: { width: '23%', backgroundColor: '#E0F2FE', borderRadius: 12, paddingVertical: 10, paddingHorizontal: 4, alignItems: 'center', }, alphabetLetter: { fontSize: 20, fontWeight: '800', color: '#0369A1', }, alphabetWord: { fontSize: 11, color: '#334155', marginTop: 4, textAlign: 'center', }, colorsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 12, }, colorCard: { flexBasis: '48%', borderRadius: 18, paddingVertical: 14, paddingHorizontal: 10, }, colorName: { fontSize: 18, fontWeight: '800', color: '#0B1120', marginBottom: 6, }, colorText: { fontSize: 13, color: 'rgba(15,23,42,0.9)', }, // Кейіпкерлер charactersRow: { flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end', marginBottom: 12, gap: 10, flexWrap: 'wrap', }, characterBubble: { paddingVertical: 10, paddingHorizontal: 12, borderRadius: 999, alignItems: 'center', justifyContent: 'center', minWidth: 90, shadowColor: '#000', shadowOpacity: 0.35, shadowRadius: 10, shadowOffset: { width: 0, height: 4 }, elevation: 6, }, characterBubbleSmall: { paddingVertical: 6, paddingHorizontal: 10, minWidth: 70, }, characterBubbleActive: { borderWidth: 2, borderColor: '#FACC15', }, characterEmoji: { fontSize: 26, }, characterLabel: { marginTop: 4, fontSize: 11, fontWeight: '700', color: '#0B1120', }, // Welcome slider carouselContainer: { height: 190, marginBottom: 12, }, carouselCard: { flex: 1, marginHorizontal: 12, borderRadius: 20, paddingVertical: 16, paddingHorizontal: 12, alignItems: 'center', justifyContent: 'center', }, carouselEmoji: { fontSize: 48, marginBottom: 4, }, carouselTitle: { fontSize: 20, fontWeight: '800', color: '#fff', marginTop: 4, }, carouselText: { fontSize: 13, color: 'rgba(255,255,255,0.95)', textAlign: 'center', marginTop: 6, }, carouselDots: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginTop: 8, }, carouselDot: { height: 8, borderRadius: 4, backgroundColor: 'rgba(255,255,255,0.9)', marginHorizontal: 4, }, // Level progress bar levelBarOuter: { height: 10, borderRadius: 999, backgroundColor: 'rgba(15,23,42,0.7)', overflow: 'hidden', marginTop: 6, }, levelBarInner: { height: '100%', borderRadius: 999, backgroundColor: '#FFD54F', }, // Daily missions missionRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, gap: 8, }, missionIcon: { fontSize: 18, }, missionText: { fontSize: 14, color: '#111827', }, missionTextDone: { textDecorationLine: 'line-through', color: '#9CA3AF', }, // Achievements achievementRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, gap: 10, }, achievementIcon: { fontSize: 20, }, achievementTitle: { fontSize: 15, fontWeight: '700', color: '#111827', }, achievementDesc: { fontSize: 12, color: '#6B7280', marginTop: 2, }, // Settings settingsRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 8, }, settingsLabel: { fontSize: 14, color: '#111827', fontWeight: '600', }, settingsValue: { fontSize: 14, color: '#374151', }, difficultyRow: { flexDirection: 'row', gap: 8, marginTop: 8, }, difficultyButton: { flex: 1, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: '#1F2937', alignItems: 'center', backgroundColor: '#0F172A', }, difficultyButtonActive: { backgroundColor: '#00E0FF', borderColor: '#00E0FF', }, difficultyText: { fontSize: 13, color: '#E5E7EB', fontWeight: '600', }, difficultyTextActive: { color: '#020617', }, // Mood moodGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 12, }, moodCard: { flexBasis: '48%', borderRadius: 18, paddingVertical: 18, paddingHorizontal: 10, alignItems: 'center', }, moodCardActive: { borderWidth: 2, borderColor: '#FACC15', }, moodEmoji: { fontSize: 30, }, moodLabel: { marginTop: 4, fontSize: 14, fontWeight: '700', color: '#111827', }, // Breathing breathingWrapper: { alignItems: 'center', marginVertical: 16, }, breathingCircle: { width: 140, height: 140, borderRadius: 70, borderWidth: 4, borderColor: 'rgba(148,163,184,0.8)', backgroundColor: 'rgba(15,23,42,0.7)', }, breathingLabel: { fontSize: 18, fontWeight: '700', textAlign: 'center', marginBottom: 8, color: '#111827', }, });

Если хочешь, следующим шагом можем:

  • добавить логотип AER019 (картинкой),
  • прикрутить реальные API (бронь, контакты),
  • или вынести тему в отдельный файл / сделать переключение «kids» ↔ «pro detailing».

Question

ты же пишешь для vscode?
и да давай переходить к следующему шагу

Thought

Determining the next step in app development

The user is referencing the next step without needing further clarification. They mentioned adding a logo, API integration, or a theme switch. Since we’ve already handled the previous step, it makes sense to proceed with adding the logo, which feels like the next natural addition to the app.

Choosing the next step and reassuring the user

The user hasn’t specified a clear next step, so I’ll proceed with adding the logo and designing a simple “About AER019” and “Contacts” screens. This seems like a natural progression. I'll also reassure the user about syntax and modules for VSCode + React Native Expo, providing file structure and code snippets for clarity.

Confirming tech setup and planning next steps

The repo is React Native/Expo, and I’ll reassure the user that VSCode works fine for this. Our next step is to add the AER019 logo to the top of the auth and home screens. I’ll also add a Contacts/Booking screen with placeholders for contact details and links. This should be good to move forward.

Choosing the next step for branding and contact screen

I think the easiest next step is adding the AER019 logo and creating a simple "About / Contacts / Promotions" screen, since this aligns well with their needs for a "детейлинг-центр." I’ll define this as Step 2 and move forward. I'll answer in Russian as requested and keep it casual.

Providing instructions for updates without full code

I'll clarify that the code is standard JS, for React Native/Expo, and can be edited in VSCode. Now, it's time for Step 2: adding the AER019 logo and the "About Center" screen. I'll give patch-like instructions, focusing on the updated code fragments, like importing Image and adding the new screen and button. This avoids confusion while offering clear guidance for modifications.

Deciding on center info and screen setup

I’ll add a constant for the center info, but I’ll label it as a placeholder for user data. The next step is implementing the renderAboutCenter screen, including the logo and contacts using `Link

Thought for 33s

Answer

Да, всё, что я пишу — обычный JS/React Native-код, идеально подходит для VS Code 👍
Ты просто редактируешь App.js в своём Expo-проекте.

Давай сделаем следующий шаг: добавим логотип AER019 и экран “О центре / контакты” прямо в приложение.


1. Картинка логотипа

  1. Создай папку, если её ещё нет:
    project-root/assets/
  2. Положи туда файл, например:
    assets/aer019-logo.png
    (любая картинка-логотип, можно временно заглушку).

2. Обновляем импорты в App.js

В самом верху, где импортируется react-native, добавь Image и Linking:

js
import { ActivityIndicator, Alert, Animated, Easing, SafeAreaView, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, useWindowDimensions, View, Image, // ← ДОБАВИЛИ Linking, // ← ДОБАВИЛИ (для открытия телефона/инсты) } from 'react-native';

3. Информация о центре AER019

Чуть ниже констант (например, после MOOD_OPTIONS или STORY_NODES) вставь блок:

js
// Информация о детейлинг-центре (заглушки — подставь свои реальные данные) const CENTER_INFO = { name: 'AER019 Detailing', slogan: 'Премиальный уход за авто + игровой формат для детей', address: 'г. Алматы, ул. Примерная, 19', // ← замени на свой адрес phone: '+7 (777) 000-19-19', // ← замени на свой телефон instagramHandle: '@aer019.detailing', // ← замени на свой инстаграм instagramUrl: 'https://instagram.com/aer019.detailing', // ← замени на свой URL workTime: 'Ежедневно 10:00–20:00', };

4. Новый экран renderAboutCenter

Чуть ниже других renderXxx-функций (например, после renderAchievements или renderSettings) добавь:

js
const renderAboutCenter = () => ( <Screen screenKey="about"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>ℹ️ О центре AER019</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <View style={{ alignItems: 'center', marginBottom: 12 }}> <Image source={require('./assets/aer019-logo.png')} style={styles.centerLogo} resizeMode="contain" /> </View> <Text style={styles.sectionTitle}>{CENTER_INFO.name}</Text> <Text style={styles.centerTagline}>{CENTER_INFO.slogan}</Text> </Card> <Card> <Text style={styles.sectionTitle}>📍 Адрес</Text> <Text style={styles.centerInfoLine}>{CENTER_INFO.address}</Text> <Text style={[styles.sectionTitle, { marginTop: 16 }]}>🕒 График работы</Text> <Text style={styles.centerInfoLine}>{CENTER_INFO.workTime}</Text> <Text style={[styles.sectionTitle, { marginTop: 16 }]}>📞 Контакты</Text> <Text style={styles.centerInfoLine}> Телефон: <Text style={styles.centerInfoLabel}>{CENTER_INFO.phone}</Text> </Text> <Text style={styles.centerInfoLine}> Instagram: <Text style={styles.centerInfoLabel}>{CENTER_INFO.instagramHandle}</Text> </Text> <View style={styles.contactButtonsRow}> <SecondaryButton title="Позвонить" onPress={() => Linking.openURL(`tel:${CENTER_INFO.phone.replace(/[^+\d]/g, '')}`)} /> <SecondaryButton title="Открыть Instagram" onPress={() => Linking.openURL(CENTER_INFO.instagramUrl)} /> </View> <Text style={styles.helperText}> Здесь можно вставить реальные контакты, акции, описание услуг и т.д. </Text> </Card> </Screen> );

5. Кнопка “О центре AER019” на главном экране

В renderHome() найди место, где идут карточки с кнопками.
Добавим ещё одну карточку для AER019 (можешь вставить её после блока “⭐ Прогресс және миссиялар” или до него).

Например, сразу после блока 🎡 Қызық аймақ вставь:

js
<Card> <Text style={styles.sectionTitle}>🏁 AER019 Detailing</Text> <Text style={styles.helperText}> Мұнда ата-аналар үшін нақты ақпарат: мекенжай, жұмыс уақыты, байланыс және т.б. </Text> <PrimaryButton title="ℹ️ О центре AER019" onPress={() => goToScreen('aboutCenter')} /> </Card>

6. Поддержка нового экрана в навигации

В самом низу, в return (...), у тебя большой тернарник:

jsx
{!isLoggedIn ? renderAuth() : currentScreen === 'home' ? renderHome() : currentScreen === 'reading' ? renderReading() ... : currentScreen === 'story' ? renderStory() : renderHome()}

Заменяем самый хвост на вариант с aboutCenter:

jsx
{!isLoggedIn ? renderAuth() : currentScreen === 'home' ? renderHome() : currentScreen === 'reading' ? renderReading() : currentScreen === 'games' ? renderGames() : currentScreen === 'quiz' ? renderQuiz() : currentScreen === 'progress' ? renderProgress() : currentScreen === 'image' ? renderImageAI() : currentScreen === 'animals' ? renderAnimals() : currentScreen === 'alphabet' ? renderAlphabet() : currentScreen === 'colors' ? renderColors() : currentScreen === 'missions' ? renderMissions() : currentScreen === 'achievements' ? renderAchievements() : currentScreen === 'settings' ? renderSettings() : currentScreen === 'mood' ? renderMood() : currentScreen === 'calm' ? renderCalm() : currentScreen === 'story' ? renderStory() : currentScreen === 'aboutCenter' ? renderAboutCenter() : renderHome()}

И не забудь: в goToScreen у нас уже всё готово — он просто делает setCurrentScreen(screenName), так что дополнительный if там не нужен.


7. Стили для логотипа и блока “О центре”

Внизу, в StyleSheet.create({ ... }), добавь к стилям:

js
centerLogo: { width: 160, height: 80, marginBottom: 6, }, centerTagline: { fontSize: 14, color: '#4B5563', marginTop: 4, }, centerInfoLine: { fontSize: 14, color: '#111827', marginTop: 4, }, centerInfoLabel: { fontWeight: '700', }, contactButtonsRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 8, marginTop: 12, },

После этого:

  1. Сохраняешь App.js в VS Code.
  2. Убедись, что файл ./assets/aer019-logo.png существует.
  3. Запускаешь проект: npx expo start.

Если всё ок — на главном экране появится новая карточка “AER019 Detailing”, а внутри — экран с логотипом и контактами.


Хочешь, следующим шагом можем:

  • сделать переключатель “Kids режим / Pro Detailing” (две темы),
  • или добавить реальную форму записи на детейлинг (имя, телефон, авто, желаемое время) с отправкой на e-mail/WhatsApp (через deep link).

Question

а ты можешь все это написать для index.tsx одним кодом

Question

так ты напишешь единый код

Question

что еще можно улучшить

Thought

Generating monolithic TypeScript file

The user wants to convert the app to TypeScript with everything in one index.tsx file. I'll take the original App.js code and convert it to TSX, adding types for states, components, and contexts. I'll integrate the necessary packages like expo-camera, expo-sqlite, expo-speech, and expo-local-authentication. I'll also suggest improvements like separating components, adding localization, API integration, theming, and analytics to enhance the app's structure.

Laying out structure for index.tsx

Alright, I need to make sure the structure of the index.tsx is correct, starting with imports from React and React Native. The default export should be function App. For SQLite, I'll use simple types like SQLite.SQLiteDatabase | null and type callbacks simply. Since the image path is ./assets/aer019-logo.png, I’ll reference that correctly with require('./assets/aer019-logo.png'). The user prefers index.tsx, so I’ll stick with that.

Ensuring TypeScript compatibility and project setup

I need to make sure the code complies with TypeScript’s strict compile rules, especially with useState generics, like const [authMode, setAuthMode] = useState<'login' | 'register'>('login');. It’s easy to forget the React import, especially with modern JSX transforms, but I’ll include it just in case. The single file may get large but it’s manageable. I’ll also define ThemeContext with its properties.

Setting up TypeScript types and handling dynamic state

For the screen navigation, I’ll define a ScreenName type with all possible screen names, like 'home' | 'reading' | 'games' | 'quiz', etc. This will prevent TypeScript from complaining about unknown strings. For CameraView from expo-camera, I’ll handle its ref as CameraView | null for TypeScript compatibility.

Typing various components for better TS compatibility

For the camera, I'll define const cameraRef = useRef<CameraView | null>(null);. I’ll handle permissions and types with useCameraPermissions(), treating requestCameraPermission as any. For SQLite, I'll type db as SQLite.SQLiteDatabase | null. For animated components, I’ll use any if types mismatch. I'll also define prop types for buttons, screens, and other components like PrimaryButtonProps and FadeInViewProps. I'll be careful with useEffect and asynchronous handling, ensuring proper type guidance through TS.

Improving TypeScript code integration and recommendations

I’ll make sure to import AsyncStorage and Expo modules correctly. I'll also type Speech.stop and split the file into modules for clarity. For improvements, I'll suggest using i18n, React Navigation, and adding type-safety for DB and API. I’ll integrate the AboutCenter screen, theme loading, and ensure the navigation updates reflect the changes.

Typing constant arrays and handling transactions

I’ll make sure to maintain proper typings for all constant arrays, like QuizQuestion[] and Animal[]. Also, I’ll define interfaces for various types, such as Achievement, MissionItem, and CharacterId. In db.transaction, I'll always check if db exists before calling it to avoid errors from null values.

Outlining TypeScript approach for a React Native app

I’ll start by organizing my imports for the code, including react, react-native, expo-camera, and AsyncStorage. I'll ensure I correctly declare types like ScreenName, AuthMode, and others. While TypeScript gives some edge cases with things like Animated.Value and useCameraPermissions, I’ll handle them. I’ll also ensure safety on imports. Let’s go ahead and keep the SafeAreaView consistent.

Thought for 2m 29s

Answer

Да, давай сделаем единый index.tsx с темой AER019, экраном “О центре”, логикой прогресса, миссий, AI-камеры и т.д.

Ниже — полный самодостаточный код.
Тебе нужно:

  • создать файл index.tsx (или App.tsx, если так в Expo),
  • убедиться, что есть картинка ./assets/aer019-logo.png,
  • установить зависимости (если чего-то не хватает):
    expo install expo-camera expo-sqlite expo-speech expo-local-authentication expo-linear-gradient @react-native-async-storage/async-storage

tsx
import React, { createContext, useContext, useEffect, useRef, useState, ReactNode, } from 'react'; import { ActivityIndicator, Alert, Animated, Easing, SafeAreaView, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, useWindowDimensions, View, Image, Linking, } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { CameraView, useCameraPermissions } from 'expo-camera'; import { LinearGradient } from 'expo-linear-gradient'; import * as LocalAuthentication from 'expo-local-authentication'; import * as Speech from 'expo-speech'; import * as SQLite from 'expo-sqlite'; // ============================= // Типы // ============================= type AuthMode = 'login' | 'register'; type AuthStep = 'credentials' | '2fa' | '3fa'; type MathLevel = 'easy' | 'medium' | 'hard'; type CharacterId = 'robot' | 'bunny' | 'lion'; type ScreenName = | 'home' | 'reading' | 'games' | 'quiz' | 'progress' | 'image' | 'animals' | 'alphabet' | 'colors' | 'missions' | 'achievements' | 'settings' | 'mood' | 'calm' | 'story' | 'aboutCenter'; interface ThemeContextValue { primaryColor: string; levelColor: string; getScreenColors: (screenKey?: string) => string[]; } interface UserData { email: string; password: string; childName: string; secretWord: string; } interface ProgressState { gamesPlayed: number; testsPassed: number; stars: number; } interface SettingsState { mathLevel: MathLevel; voiceHints: boolean; chosenCharacterId: CharacterId; } interface MissionItem { id: string; title: string; done: boolean; } interface MissionsState { date: string; items: MissionItem[]; rewardCollected: boolean; } interface QuizQuestion { text: string; options: string[]; correctIndex: number; } interface AnimalItem { id: string; name: string; emoji: string; } interface AlphabetItem { letter: string; word: string; } interface ColorLesson { id: string; name: string; text: string; bg: string; } type AchievementType = 'stars' | 'gamesPlayed' | 'testsPassed'; interface Achievement { id: string; type: AchievementType; threshold: number; title: string; description: string; } interface MoodOption { id: string; label: string; emoji: string; color: string; } interface StoryChoice { id: string; text: string; nextId?: string; starsDelta?: number; } interface StoryNode { id: string; title: string; text: string; choices: StoryChoice[]; isEnd?: boolean; } interface Character { id: CharacterId; emoji: string; label: string; color: string; } interface WelcomeSlide { key: string; title: string; text: string; emoji: string; background: string[]; } interface ThemeDescriptor { primaryColor: string; levelColor: string; screenGradients: Record<string, string[]>; } // ============================= // Тема приложения (контекст) // ============================= const ThemeContext = createContext<ThemeContextValue>({ primaryColor: '#00E0FF', levelColor: '#FFD54F', getScreenColors: () => ['#020617', '#050816', '#0B1120'], }); // ============================= // SQLite // ============================= let db: SQLite.SQLiteDatabase | null = null; try { if (SQLite && typeof SQLite.openDatabase === 'function') { db = SQLite.openDatabase('ai_kids.db'); } else { console.warn('SQLite.openDatabase не найден. Используем только AsyncStorage.'); } } catch (e) { console.warn('Ошибка при открытии SQLite. Используем только AsyncStorage.', e); } // ============================= // Данные / Константы // ============================= const CHARACTERS: Character[] = [ { id: 'robot', emoji: '🚗', label: 'AER019 Бот', color: '#00E0FF' }, { id: 'bunny', emoji: '🧽', label: 'Detail Master', color: '#F97316' }, { id: 'lion', emoji: '✨', label: 'Shine Pro', color: '#22C55E' }, ]; const WELCOME_SLIDES: WelcomeSlide[] = [ { key: 'slide1', title: 'Умный гараж AER019', text: 'Игровой формат обучения уходу за авто, безопасности и технологиям.', emoji: '🚗', background: ['#020617', '#0369A1'], }, { key: 'slide2', title: 'Голосовой помощник', text: 'Читаем текст вслух, подсказываем и мотивируем ребёнка.', emoji: '🎧', background: ['#0B1120', '#00E0FF'], }, { key: 'slide3', title: 'AI-камера в боксе', text: 'Камера и AI помогают «угадывать» предметы вокруг автомобиля.', emoji: '📷', background: ['#020617', '#22C55E'], }, ]; const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity as any); // Темы персонажей const THEMES: Record<CharacterId, ThemeDescriptor> = { robot: { primaryColor: '#00E0FF', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#050816', '#0B1120'], auth: ['#020617', '#0B1120'], home: ['#020617', '#050816', '#0B1120'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#22C55E'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, bunny: { primaryColor: '#00E0FF', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#050816', '#0B1120'], auth: ['#020617', '#0B1120'], home: ['#020617', '#020617', '#0369A1'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#22C55E'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, lion: { primaryColor: '#F97316', levelColor: '#FFD54F', screenGradients: { default: ['#020617', '#0B1120', '#111827'], auth: ['#020617', '#111827'], home: ['#020617', '#0B1120', '#F97316'], reading: ['#020617', '#0369A1'], games: ['#020617', '#0B1120', '#F97316'], quiz: ['#020617', '#0B1120', '#00E0FF'], progress: ['#020617', '#0B1120', '#FFD54F'], image: ['#020617', '#0B1120', '#00E0FF'], animals: ['#020617', '#0B1120', '#22C55E'], alphabet: ['#020617', '#0369A1'], colors: ['#020617', '#0B1120', '#F97316'], missions: ['#020617', '#0B1120', '#22C55E'], achievements: ['#020617', '#0B1120', '#FFD54F'], settings: ['#020617', '#0B1120'], mood: ['#020617', '#0B1120', '#00E0FF'], calm: ['#020617', '#0B1120', '#22C55E'], story: ['#020617', '#0B1120', '#00E0FF'], }, }, }; const QUIZ_DATA: QuizQuestion[] = [ { text: '1) Детейлинг дегеніміз не?', options: [ 'А) Тек жылдам жуу', 'Ә) Көлікті терең тазалау және қорғау', 'Б) Дөңгелек ауыстыру', 'В) Тек салон иісін жаңарту', ], correctIndex: 1, }, { text: '2) Көлікті не үшін жиі жуу керек?', options: [ 'А) Тек фотоға әдемі шығу үшін', 'Ә) Лак-бояу жабынын қорғау және коррозияны азайту үшін', 'Б) Тек шыныны тазарту үшін', 'В) Мүлде қажет емес', ], correctIndex: 1, }, { text: '3) AER019 орталығында не істейміз?', options: [ 'А) Көлікті кәсіби түрде тазалап, қорғаймыз', 'Ә) Тек ойын ойнаймыз', 'Б) Тек жанармай құямыз', 'В) Тек доңғалақ сатамыз', ], correctIndex: 0, }, ]; const READING_TEXT = 'Бүгін біз AER019 Detailing орталығында көлікке қалай дұрыс қарау керектігін үйренеміз. ' + 'Жасанды интеллект пен ойын форматындағы тапсырмалар балаларға қауіпсіздік пен ұқыптылықты түсінуге көмектеседі.'; const ANIMAL_QUIZ: AnimalItem[] = [ { id: 'cat', name: 'Мысық', emoji: '🐱' }, { id: 'dog', name: 'Ит', emoji: '🐶' }, { id: 'cow', name: 'Сиыр', emoji: '🐮' }, { id: 'horse', name: 'Жылқы', emoji: '🐴' }, { id: 'chicken', name: 'Тауық', emoji: '🐔' }, { id: 'lion', name: 'Арыстан', emoji: '🦁' }, ]; const KAZAKH_ALPHABET: AlphabetItem[] = [ { letter: 'А', word: 'авто' }, { letter: 'Ә', word: 'әдемі жылтыр' }, { letter: 'Б', word: 'балапан жүргізуші' }, { letter: 'В', word: 'вагон' }, { letter: 'Г', word: 'гүлді гараж' }, { letter: 'Ғ', word: 'ғалам' }, { letter: 'Д', word: 'детейлинг' }, { letter: 'Е', word: 'ерекше күтім' }, { letter: 'Ж', word: 'жуу' }, { letter: 'З', word: 'жылдамдық (скорость)' }, { letter: 'И', word: 'игі іс' }, { letter: 'Й', word: 'йогурт' }, { letter: 'К', word: 'көлік' }, { letter: 'Қ', word: 'қорғау' }, { letter: 'Л', word: 'лак' }, { letter: 'М', word: 'май алмастыру' }, { letter: 'Н', word: 'нұрлы шамдар' }, { letter: 'Ң', word: 'ңғой (мысал)' }, { letter: 'О', word: 'ойын' }, { letter: 'Ө', word: 'өрік' }, { letter: 'П', word: 'полировка' }, { letter: 'Р', word: 'резина (дөңгелек)' }, { letter: 'С', word: 'сусабын (шампунь)' }, { letter: 'Т', word: 'тазалық' }, { letter: 'У', word: 'унил' }, { letter: 'Ұ', word: 'ұқыптылық' }, { letter: 'Ү', word: 'үйрену' }, { letter: 'Ф', word: 'финиш жабыны' }, { letter: 'Х', word: 'химиялық тазалау' }, { letter: 'Һ', word: 'һә' }, { letter: 'Ц', word: 'цирк' }, { letter: 'Ч', word: 'чехол' }, { letter: 'Ш', word: 'шина' }, { letter: 'Щ', word: 'щетка' }, { letter: 'Ы', word: 'ырыс' }, { letter: 'І', word: 'інді' }, { letter: 'Э', word: 'экран' }, { letter: 'Ю', word: 'юла' }, { letter: 'Я', word: 'якорь' }, ]; const COLOR_LESSONS: ColorLesson[] = [ { id: 'red', name: 'Қызыл', text: 'Қызыл – спорткардың түсі', bg: '#EF4444' }, { id: 'blue', name: 'Көк', text: 'Көк – аспан мен көк седан түсі', bg: '#3B82F6' }, { id: 'green', name: 'Жасыл', text: 'Жасыл – экокардың түсі', bg: '#22C55E' }, { id: 'yellow', name: 'Сары', text: 'Сары – такси мен күннің түсі', bg: '#FACC15' }, ]; const ACHIEVEMENTS: Achievement[] = [ { id: 'firstStar', type: 'stars', threshold: 1, title: 'Алғашқы кузов', description: '1 жұлдыз жинадың. AER019 әлеміне қош келдің!', }, { id: 'tenStars', type: 'stars', threshold: 10, title: 'Мини-бокс шебері', description: '10 жұлдыз жинадың. Сен нағыз көмекші-механиксің.', }, { id: 'games5', type: 'gamesPlayed', threshold: 5, title: 'Ойын шебері', description: '5 ойын ойнадың. Тапсырмалар саған ұнайды!', }, { id: 'tests3', type: 'testsPassed', threshold: 3, title: 'Білімгер механик', description: '3 тесттен сәтті өттің.', }, { id: 'stars25', type: 'stars', threshold: 25, title: 'Супер гараж', description: '25 жұлдыз жинадың. Сен AER019 жұлдызысың!', }, ]; const MOOD_OPTIONS: MoodOption[] = [ { id: 'happy', label: 'Көңілдімін', emoji: '😊', color: '#FACC15' }, { id: 'ok', label: 'Жақсы', emoji: '🙂', color: '#22C55E' }, { id: 'tired', label: 'Шаршадым', emoji: '😴', color: '#A855F7' }, { id: 'sad', label: 'Көңілсіз', emoji: '😕', color: '#3B82F6' }, ]; const STORY_NODES: Record<string, StoryNode> = { start: { id: 'start', title: '1-көрініс: Таңғы гараж', text: 'Бүгін AER019 Бот сені оятты. Көлікті күту күні! Не істейсің?', choices: [ { id: 'fast', text: 'Тез жуынуға барып, боксқа дайындаламын', nextId: 'school', starsDelta: 1 }, { id: 'sleep', text: 'Тағы 5 минут ұйықтаймын', nextId: 'late' }, ], }, school: { id: 'school', title: '2-көрініс: Жолға дайындық', text: 'Жолда досыңды көрдің. Ол да AER019 туралы білгісі келеді.', choices: [ { id: 'friend', text: 'Бірге барып, балалар бұрышына кіреміз', nextId: 'classFun', starsDelta: 1 }, { id: 'rush', text: 'Мен асығып тұрмын, кейін айтамын', nextId: 'classSolo' }, ], }, late: { id: 'late', title: '2-көрініс: Кешігу', text: 'Сен гаражға кешігіп келдің. Механик сені күтіп тұр.', choices: [ { id: 'apologize', text: 'Кешіккенім үшін кешірім сұраймын', nextId: 'classSolo', starsDelta: 1, }, { id: 'silent', text: 'Ештеңе демеймін', nextId: 'endingOk' }, ], }, classFun: { id: 'classFun', title: '3-көрініс: Бірге үйрену', text: 'Сен досыңмен бірге «қос шелек әдісі» мен қауіпсіздік ережелерін үйрендің.', choices: [ { id: 'endGood', text: 'Күнім өте керемет өтті! 🎉', nextId: 'endingGood', starsDelta: 1 }, ], }, classSolo: { id: 'classSolo', title: '3-көрініс: Жауапкершілік', text: 'Сен нұсқауларды тыңдап, барлық тапсырманы мұқият орындайсың.', choices: [ { id: 'endCalm', text: 'Бүгін жақсы еңбек еттім деп ойлаймын 😊', nextId: 'endingGood', starsDelta: 1, }, ], }, endingGood: { id: 'endingGood', title: 'Қорытынды: Керемет күн', text: 'Сен дұрыс таңдаулар жасап, пайдалы күн өткіздің. AER019 Бот сенімен мақтанады!', choices: [], isEnd: true, }, endingOk: { id: 'endingOk', title: 'Қорытынды: Ойланатын күн', text: 'Бүгін бәрі онша болмады, бірақ келесі жолы жақсырақ істеуге болады!', choices: [], isEnd: true, }, }; // Информация о центре (замени на реальные данные) const CENTER_INFO = { name: 'AER019 Detailing', slogan: 'Премиальный уход за авто + игровой формат для детей', address: 'г. Алматы, ул. Примерная, 19', phone: '+7 (777) 000-19-19', instagramHandle: '@aer019.detailing', instagramUrl: 'https://instagram.com/aer019.detailing', workTime: 'Ежедневно 10:00–20:00', }; // ============================= // Хелперы / компоненты // ============================= interface PrimaryButtonProps { title: string; onPress: () => void; } const PrimaryButton: React.FC<PrimaryButtonProps> = ({ title, onPress }) => { const { primaryColor } = useContext(ThemeContext); const scale = useRef(new Animated.Value(1)).current; const handlePressIn = () => { Animated.spring(scale, { toValue: 0.93, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; const handlePressOut = () => { Animated.spring(scale, { toValue: 1, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; return ( <AnimatedTouchableOpacity style={[ styles.primaryButton, { backgroundColor: primaryColor, transform: [{ scale }], }, ]} onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut} activeOpacity={0.9} > <Text style={styles.primaryButtonText}>{title}</Text> </AnimatedTouchableOpacity> ); }; interface SecondaryButtonProps { title: string; onPress: () => void; light?: boolean; } const SecondaryButton: React.FC<SecondaryButtonProps> = ({ title, onPress, light }) => { const scale = useRef(new Animated.Value(1)).current; const handlePressIn = () => { Animated.spring(scale, { toValue: 0.95, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; const handlePressOut = () => { Animated.spring(scale, { toValue: 1, friction: 4, tension: 80, useNativeDriver: true, }).start(); }; return ( <AnimatedTouchableOpacity style={[ styles.secondaryButton, light && styles.secondaryButtonLight, { transform: [{ scale }] }, ]} onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut} activeOpacity={0.9} > <Text style={[styles.secondaryButtonText, light && styles.secondaryButtonTextDark]}> {title} </Text> </AnimatedTouchableOpacity> ); }; interface CardProps { children: ReactNode; } const Card: React.FC<CardProps> = ({ children }) => { const scale = useRef(new Animated.Value(0.96)).current; const opacity = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.parallel([ Animated.timing(scale, { toValue: 1, duration: 300, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true, }), ]).start(); }, [opacity, scale]); return ( <Animated.View style={[styles.card, { transform: [{ scale }], opacity }]}> {children} </Animated.View> ); }; interface ScreenProps { colors?: string[]; screenKey?: string; children: ReactNode; } const Screen: React.FC<ScreenProps> = ({ colors, screenKey, children }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const translateY = useRef(new Animated.Value(24)).current; const { getScreenColors } = useContext(ThemeContext); const gradientColors = colors || getScreenColors(screenKey || 'default'); useEffect(() => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration: 450, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(translateY, { toValue: 0, duration: 450, easing: Easing.out(Easing.quad), useNativeDriver: true, }), ]).start(); }, [fadeAnim, translateY]); return ( <LinearGradient colors={gradientColors} style={styles.gradient}> <Animated.View style={{ flex: 1, opacity: fadeAnim, transform: [{ translateY }], }} > <ScrollView contentContainerStyle={styles.scroll}>{children}</ScrollView> </Animated.View> </LinearGradient> ); }; interface FadeInViewProps { children: ReactNode; style?: any; duration?: number; delay?: number; } const FadeInView: React.FC<FadeInViewProps> = ({ children, style, duration = 600, delay = 0, }) => { const fadeAnim = useRef(new Animated.Value(0)).current; const translateY = useRef(new Animated.Value(16)).current; useEffect(() => { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, duration, delay, useNativeDriver: true, }), Animated.timing(translateY, { toValue: 0, duration, delay, easing: Easing.out(Easing.quad), useNativeDriver: true, }), ]).start(); }, [delay, duration, fadeAnim, translateY]); return ( <Animated.View style={[ style, { opacity: fadeAnim, transform: [{ translateY }], }, ]} > {children} </Animated.View> ); }; interface BouncyCharactersProps { small?: boolean; } const BouncyCharacters: React.FC<BouncyCharactersProps> = ({ small }) => { const bounce = useRef(new Animated.Value(0)).current; useEffect(() => { Animated.loop( Animated.sequence([ Animated.timing(bounce, { toValue: -10, duration: 600, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(bounce, { toValue: 0, duration: 600, easing: Easing.in(Easing.quad), useNativeDriver: true, }), ]), ).start(); }, [bounce]); return ( <View style={styles.charactersRow}> {CHARACTERS.map((ch) => ( <Animated.View key={ch.id} style={[ styles.characterBubble, small && styles.characterBubbleSmall, { backgroundColor: ch.color, transform: [{ translateY: bounce }], }, ]} > <Text style={styles.characterEmoji}>{ch.emoji}</Text> <Text style={styles.characterLabel}>{ch.label}</Text> </Animated.View> ))} </View> ); }; const WelcomeCarousel: React.FC = () => { const scrollX = useRef(new Animated.Value(0)).current; const { width } = useWindowDimensions(); return ( <View style={styles.carouselContainer}> <Animated.ScrollView horizontal pagingEnabled showsHorizontalScrollIndicator={false} style={{ flexGrow: 0 }} onScroll={Animated.event( [{ nativeEvent: { contentOffset: { x: scrollX } } }], { useNativeDriver: false }, )} scrollEventThrottle={16} > {WELCOME_SLIDES.map((slide) => ( <View key={slide.key} style={{ width }}> <LinearGradient colors={slide.background} style={styles.carouselCard}> <Text style={styles.carouselEmoji}>{slide.emoji}</Text> <Text style={styles.carouselTitle}>{slide.title}</Text> <Text style={styles.carouselText}>{slide.text}</Text> </LinearGradient> </View> ))} </Animated.ScrollView> <View style={styles.carouselDots}> {WELCOME_SLIDES.map((_, index) => { const inputRange = [ (index - 1) * width, index * width, (index + 1) * width, ]; const dotWidth = scrollX.interpolate({ inputRange, outputRange: [8, 20, 8], extrapolate: 'clamp', }); return <Animated.View key={index} style={[styles.carouselDot, { width: dotWidth }]} />; })} </View> </View> ); }; interface LevelProgressBarProps { stars: number; } const LevelProgressBar: React.FC<LevelProgressBarProps> = ({ stars }) => { const { levelColor } = useContext(ThemeContext); const progressAnim = useRef(new Animated.Value(0)).current; useEffect(() => { const starsInLevel = stars % 5; const progress = starsInLevel / 5; Animated.timing(progressAnim, { toValue: progress, duration: 500, easing: Easing.out(Easing.quad), useNativeDriver: false, }).start(); }, [progressAnim, stars]); const widthInterpolated = progressAnim.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%'], }); return ( <View style={styles.levelBarOuter}> <Animated.View style={[ styles.levelBarInner, { width: widthInterpolated, backgroundColor: levelColor }, ]} /> </View> ); }; interface BreathingCircleProps { playing: boolean; } const BreathingCircle: React.FC<BreathingCircleProps> = ({ playing }) => { const scale = useRef(new Animated.Value(1)).current; useEffect(() => { let animation: Animated.CompositeAnimation | null = null; if (playing) { animation = Animated.loop( Animated.sequence([ Animated.timing(scale, { toValue: 1.25, duration: 4000, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }), Animated.timing(scale, { toValue: 0.9, duration: 4000, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }), ]), ); animation.start(); } else { scale.setValue(1); } return () => { if (animation) { animation.stop(); } }; }, [playing, scale]); return ( <View style={styles.breathingWrapper}> <Animated.View style={[styles.breathingCircle, { transform: [{ scale }] }]} /> </View> ); }; const speakKk = (text: string) => { Speech.stop(); Speech.speak(text, { language: 'kk-KZ', rate: 0.95, pitch: 1.0, }); }; // ============================= // Основное приложение // ============================= const App: React.FC = () => { // Auth const [authMode, setAuthMode] = useState<AuthMode>('login'); const [authStep, setAuthStep] = useState<AuthStep>('credentials'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [childName, setChildName] = useState(''); const [secretWord, setSecretWord] = useState(''); const [secretWordLogin, setSecretWordLogin] = useState(''); const [twoFaCode, setTwoFaCode] = useState(''); const [generatedCode, setGeneratedCode] = useState(''); const [savedUser, setSavedUser] = useState<UserData | null>(null); const [isLoggedIn, setIsLoggedIn] = useState(false); // Навигация const [currentScreen, setCurrentScreen] = useState<ScreenName>('home'); // Прогресс const [progress, setProgress] = useState<ProgressState>({ gamesPlayed: 0, testsPassed: 0, stars: 0, }); // Настройки const [settings, setSettings] = useState<SettingsState>({ mathLevel: 'easy', voiceHints: true, chosenCharacterId: 'robot', }); // Миссии const [missions, setMissions] = useState<MissionsState>({ date: '', items: [], rewardCollected: false, }); // Математическая игра const [gameA, setGameA] = useState(0); const [gameB, setGameB] = useState(0); const [gameOp, setGameOp] = useState<'+' | '-' >('+'); const [gameOptions, setGameOptions] = useState<number[]>([]); const [gameMessage, setGameMessage] = useState(''); // Тест const [quizIndex, setQuizIndex] = useState(0); const [quizCorrect, setQuizCorrect] = useState(0); const [quizFinished, setQuizFinished] = useState(false); // Камера / AI const cameraRef = useRef<any>(null); const [cameraPermission, requestCameraPermission] = useCameraPermissions(); const [cameraOpen, setCameraOpen] = useState(false); const [aiResult, setAiResult] = useState(''); const [aiLoading, setAiLoading] = useState(false); // Жануарлар const [animalQuestion, setAnimalQuestion] = useState<AnimalItem | null>(ANIMAL_QUIZ[0]); const [animalOptions, setAnimalOptions] = useState<AnimalItem[]>([]); const [animalMessage, setAnimalMessage] = useState(''); // Көңіл-күй const [moodToday, setMoodToday] = useState<string | null>(null); const [moodMessage, setMoodMessage] = useState(''); // Тыныс алу const [breathingPlaying, setBreathingPlaying] = useState(false); const [breathingLabel, setBreathingLabel] = useState('Дайынсың ба?'); const [breathingMessage, setBreathingMessage] = useState(''); // История const [storyNodeId, setStoryNodeId] = useState<string>('start'); const [storyEnded, setStoryEnded] = useState(false); // Тема const currentThemeKey: CharacterId = settings.chosenCharacterId || 'robot'; const baseTheme = THEMES[currentThemeKey] || THEMES.robot; const themeValue: ThemeContextValue = { primaryColor: baseTheme.primaryColor, levelColor: baseTheme.levelColor, getScreenColors: (screenKey?: string) => { const gradients = baseTheme.screenGradients || {}; if (screenKey && gradients[screenKey]) return gradients[screenKey]; if (gradients.default) return gradients.default; return ['#020617', '#050816', '#0B1120']; }, }; // Хелперы const getTodayKey = () => { const d = new Date(); const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${d.getFullYear()}-${mm}-${dd}`; }; const createDefaultMissions = (): MissionItem[] => [ { id: 'playGame', title: '1 математикалық ойын ойна', done: false }, { id: 'doQuiz', title: '1 тесттен өт', done: false }, { id: 'readText', title: 'Мәтінді дауыспен тыңда', done: false }, { id: 'chooseMood', title: 'Көңіл-күйіңді таңда', done: false }, { id: 'calmSession', title: '1 тыныс алу жаттығуын орында', done: false }, { id: 'storyPlay', title: 'Таңдау хикаясын бір рет ойна', done: false }, ]; const updateMissions = (updater: (prev: MissionsState) => MissionsState) => { setMissions((prev) => { const nextState = updater(prev); AsyncStorage.setItem('aiKidsMissions', JSON.stringify(nextState)).catch((e) => { console.log('Save missions error', e); }); return nextState; }); }; const completeMission = (id: string) => { updateMissions((prev) => { if (!prev.items || prev.items.length === 0) return prev; const items = prev.items.map((m) => (m.id === id ? { ...m, done: true } : m)); return { ...prev, items }; }); }; const updateSettings = (patch: Partial<SettingsState>) => { setSettings((prev) => { const updated = { ...prev, ...patch }; AsyncStorage.setItem('aiKidsSettings', JSON.stringify(updated)).catch((e) => console.log('Save settings error', e), ); return updated; }); }; const speakIfEnabled = (text: string) => { if (!settings.voiceHints) return; speakKk(text); }; // Инициализация: SQLite + AsyncStorage useEffect(() => { if (db) { db.transaction((tx) => { tx.executeSql( `CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY NOT NULL, email TEXT, password TEXT, childName TEXT, secretWord TEXT );`, ); tx.executeSql( `CREATE TABLE IF NOT EXISTS progress ( id INTEGER PRIMARY KEY NOT NULL, gamesPlayed INTEGER, testsPassed INTEGER, stars INTEGER );`, ); }); } const loadData = async () => { try { if (db) { // User db.transaction((tx) => { tx.executeSql( 'SELECT email, password, childName, secretWord FROM user LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const user = rows._array[0] as UserData; setSavedUser(user); await AsyncStorage.setItem('aiKidsUser', JSON.stringify(user)); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) { setSavedUser(JSON.parse(jsonUser)); } } }, (_, error) => { console.log('User select error', error); return false; }, ); }); // Progress db.transaction((tx) => { tx.executeSql( 'SELECT gamesPlayed, testsPassed, stars FROM progress LIMIT 1;', [], async (_, { rows }) => { if (rows.length > 0) { const p = rows._array[0] as any; const loaded: ProgressState = { gamesPlayed: p.gamesPlayed ?? 0, testsPassed: p.testsPassed ?? 0, stars: p.stars ?? 0, }; setProgress(loaded); await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(loaded)); } else { const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) { setProgress(JSON.parse(jsonProgress)); } } }, (_, error) => { console.log('Progress select error', error); return false; }, ); }); } else { const jsonUser = await AsyncStorage.getItem('aiKidsUser'); if (jsonUser) setSavedUser(JSON.parse(jsonUser)); const jsonProgress = await AsyncStorage.getItem('aiKidsProgress'); if (jsonProgress) setProgress(JSON.parse(jsonProgress)); } // Settings const jsonSettings = await AsyncStorage.getItem('aiKidsSettings'); if (jsonSettings) { setSettings(JSON.parse(jsonSettings)); } const today = getTodayKey(); // Missions const jsonMissions = await AsyncStorage.getItem('aiKidsMissions'); if (jsonMissions) { try { const saved = JSON.parse(jsonMissions) as MissionsState; if (saved.date === today) { setMissions(saved); } else { const fresh: MissionsState = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } catch (err) { console.log('Parse missions error', err); const fresh: MissionsState = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } } else { const fresh: MissionsState = { date: today, items: createDefaultMissions(), rewardCollected: false, }; setMissions(fresh); await AsyncStorage.setItem('aiKidsMissions', JSON.stringify(fresh)); } // Mood const jsonMood = await AsyncStorage.getItem('aiKidsMood'); if (jsonMood) { try { const savedMood = JSON.parse(jsonMood) as { date: string; moodId: string }; if (savedMood.date === today) { setMoodToday(savedMood.moodId); } } catch (e) { console.log('Parse mood error', e); } } } catch (e) { console.log('Load storage / SQL error', e); } }; loadData(); }, []); // Прогресс const addProgress = async ({ gamesDelta = 0, testsDelta = 0, starsDelta = 0, }: { gamesDelta?: number; testsDelta?: number; starsDelta?: number; }) => { try { const updated: ProgressState = { gamesPlayed: progress.gamesPlayed + gamesDelta, testsPassed: progress.testsPassed + testsDelta, stars: progress.stars + starsDelta, }; setProgress(updated); await AsyncStorage.setItem('aiKidsProgress', JSON.stringify(updated)); if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM progress;'); tx.executeSql( 'INSERT INTO progress (gamesPlayed, testsPassed, stars) VALUES (?, ?, ?);', [updated.gamesPlayed, updated.testsPassed, updated.stars], () => console.log('Progress inserted into SQLite'), (_, error) => { console.log('Progress insert error', error); return false; }, ); }); } } catch (e) { console.log('Save progress error', e); } }; const collectMissionReward = () => { setMissions((prev) => { if (!prev.items || prev.items.length === 0) { Alert.alert('Миссиялар', 'Бүгінгі миссиялар табылмады.'); return prev; } const allDone = prev.items.every((m) => m.done); if (!allDone) { Alert.alert('Миссиялар', 'Алдымен барлық миссияны орындап шық.'); return prev; } if (prev.rewardCollected) { Alert.alert('Миссиялар', 'Бүгінгі сыйлықты бұрын алғансың.'); return prev; } addProgress({ starsDelta: 5 }); speakIfEnabled('Керемет! Күнделікті миссиялар үшін қосымша 5 жұлдыз алдың!'); const updated = { ...prev, rewardCollected: true }; AsyncStorage.setItem('aiKidsMissions', JSON.stringify(updated)).catch((err) => console.log('Save missions reward error', err), ); return updated; }); }; // Тыныс алу useEffect(() => { let intervalId: any; let timeoutId: any; if (breathingPlaying) { let phase = 0; setBreathingLabel('Дем ал'); setBreathingMessage(''); intervalId = setInterval(() => { phase = 1 - phase; setBreathingLabel(phase === 0 ? 'Дем ал' : 'Дем шығар'); }, 4000); timeoutId = setTimeout(() => { setBreathingPlaying(false); const msg = 'Керемет! Тыныс алу жаттығуын аяқтадың, 1 жұлдыз сыйлаймын.'; setBreathingMessage(msg); speakIfEnabled(msg); addProgress({ starsDelta: 1 }); completeMission('calmSession'); }, 24000); } else { setBreathingLabel('Дайынсың ба?'); } return () => { if (intervalId) clearInterval(intervalId); if (timeoutId) clearTimeout(timeoutId); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [breathingPlaying]); // Auth const handleRegister = async () => { if (!email || !password || !childName || !secretWord) { Alert.alert('Қате', 'Барлық өрістерді толтырыңыз.'); return; } try { const newUser: UserData = { email, password, childName, secretWord }; await AsyncStorage.setItem('aiKidsUser', JSON.stringify(newUser)); setSavedUser(newUser); if (db) { db.transaction((tx) => { tx.executeSql('DELETE FROM user;'); tx.executeSql( 'INSERT INTO user (email, password, childName, secretWord) VALUES (?, ?, ?, ?);', [newUser.email, newUser.password, newUser.childName, newUser.secretWord], () => console.log('User inserted into SQLite'), (_, error) => { console.log('User insert error', error); return false; }, ); }); } Alert.alert('Тіркелу', 'Аккаунт сәтті жасалды! Енді кіруге болады.'); setAuthMode('login'); setAuthStep('credentials'); setPassword(''); setSecretWord(''); } catch (e) { Alert.alert('Қате', 'Пайдаланушыны сақтау кезінде қате кетті.'); } }; const handleLoginCredentials = () => { if (!savedUser) { Alert.alert('Ақпарат', 'Алдымен тіркеліп алыңыз.'); return; } if (email === savedUser.email && password === savedUser.password) { const code = Math.floor(100000 + Math.random() * 900000).toString(); setGeneratedCode(code); setAuthStep('2fa'); setTwoFaCode(''); Alert.alert( '2FA коды', `Демо режим: әдетте бұл код SMS немесе e-mail арқылы келеді.\nҚазір тек тест үшін код: ${code}`, ); } else { Alert.alert('Қате', 'E-mail немесе құпия сөз дұрыс емес.'); } }; const handleCheck2FA = () => { if (twoFaCode === generatedCode) { setAuthStep('3fa'); } else { Alert.alert('Қате', '2FA коды дұрыс емес.'); } }; const finishLogin = () => { setIsLoggedIn(true); setCurrentScreen('home'); setAuthStep('credentials'); setSecretWordLogin(''); setTwoFaCode(''); }; const handleThreeFA = async () => { try { const hasHardware = await LocalAuthentication.hasHardwareAsync(); if (hasHardware) { const result = await LocalAuthentication.authenticateAsync({ promptMessage: 'AER019 Kids App - 3FA тексеру', fallbackLabel: 'Қауіпсіздік сөзімен растау', }); if (result.success) { finishLogin(); return; } } if (savedUser && secretWordLogin === savedUser.secretWord) { finishLogin(); } else { Alert.alert('3FA', 'Қауіпсіздік сөзі дұрыс емес.'); } } catch (e) { Alert.alert('Қате', '3FA тексеру кезінде қате кетті.'); } }; const handleLogout = () => { setIsLoggedIn(false); setCurrentScreen('home'); setEmail(''); setPassword(''); setAuthStep('credentials'); }; // Навигация и подготовка игр const setupMathGame = () => { const createNumber = () => { if (settings.mathLevel === 'easy') { return Math.floor(Math.random() * 10) + 1; } if (settings.mathLevel === 'medium') { return Math.floor(Math.random() * 16) + 5; } return Math.floor(Math.random() * 41) + 10; }; let a = createNumber(); let b = createNumber(); let op: '+' | '-' = '+'; if (settings.mathLevel === 'easy') { op = '+'; } else { op = Math.random() < 0.5 ? '+' : '-'; } let correct: number; if (op === '-') { if (a < b) [a, b] = [b, a]; correct = a - b; } else { correct = a + b; } const options = new Set<number>([correct]); const variation = settings.mathLevel === 'hard' ? 10 : 5; while (options.size < 4) { const fake = correct + (Math.floor(Math.random() * (variation * 2 + 1)) - variation); if (fake > 0 && fake !== correct) options.add(fake); } const optionsArray = Array.from(options); optionsArray.sort(() => Math.random() - 0.5); setGameA(a); setGameB(b); setGameOp(op); setGameOptions(optionsArray); setGameMessage(''); }; const setupAnimalRound = () => { const correct = ANIMAL_QUIZ[Math.floor(Math.random() * ANIMAL_QUIZ.length)]; const shuffled = [...ANIMAL_QUIZ].sort(() => Math.random() - 0.5); const options: AnimalItem[] = [correct]; for (const item of shuffled) { if (options.length >= 3) break; if (item.id !== correct.id) options.push(item); } options.sort(() => Math.random() - 0.5); setAnimalQuestion(correct); setAnimalOptions(options); setAnimalMessage(''); }; const resetQuiz = () => { setQuizIndex(0); setQuizCorrect(0); setQuizFinished(false); }; const resetStory = () => { setStoryNodeId('start'); setStoryEnded(false); }; const goToScreen = (screenName: ScreenName) => { if (screenName === 'games') { setupMathGame(); } if (screenName === 'quiz') { resetQuiz(); } if (screenName === 'animals') { setupAnimalRound(); } if (screenName === 'story') { resetStory(); } setCurrentScreen(screenName); }; // Дауыспен оқу const handleSpeakReading = () => { speakIfEnabled(READING_TEXT); completeMission('readText'); }; const handleStopSpeak = () => { Speech.stop(); }; // Математическая игра const handleGameAnswer = (value: number) => { const correct = gameOp === '-' ? gameA - gameB : gameA + gameB; if (value === correct) { const msg = 'Керемет! Дұрыс жауап! ⭐'; setGameMessage(msg); speakIfEnabled(msg); addProgress({ gamesDelta: 1, starsDelta: 1 }); completeMission('playGame'); setTimeout(setupMathGame, 700); } else { const msg = 'Әлі де тырысып көр! Қолыңнан келеді!'; setGameMessage(msg); speakIfEnabled(msg); } }; // Тест const handleQuizAnswer = (index: number) => { const currentQ = QUIZ_DATA[quizIndex]; if (index === currentQ.correctIndex) { setQuizCorrect((prev) => prev + 1); } if (quizIndex + 1 < QUIZ_DATA.length) { setQuizIndex((prev) => prev + 1); } else { setQuizFinished(true); addProgress({ testsDelta: 1, starsDelta: QUIZ_DATA.length, }); completeMission('doQuiz'); } }; // Камера + AI const openCamera = async () => { try { let permission = cameraPermission; if (!permission || !permission.granted) { const result = await requestCameraPermission(); permission = result; } if (!permission || !permission.granted) { Alert.alert('Рұқсат жоқ', 'Камераға рұқсат беру қажет.'); return; } setCameraOpen(true); setAiResult(''); } catch (e) { Alert.alert('Қате', 'Камера рұқсатын сұрау кезінде қате кетті.'); } }; const handleTakePicture = async () => { if (!cameraRef.current) return; try { setAiLoading(true); const photo = await cameraRef.current.takePictureAsync({ base64: true, quality: 0.3, }); const labels = ['көлік', 'доп', 'шелек', 'шина', 'кітап', 'адам']; const randomLabel = labels[Math.floor(Math.random() * labels.length)]; const text = `AI болжамы: ${randomLabel}. Бұл тек демо, нақты модельмен алмастыруға болады.`; console.log('demo photo base64 length:', photo?.base64?.length || 'no-base64'); setAiResult(text); speakIfEnabled(text); } catch (e) { Alert.alert('Қате', 'Сурет түсіру кезінде қате кетті.'); } finally { setAiLoading(false); setCameraOpen(false); } }; // Жануарлар const handleAnimalAnswer = (animalId: string) => { if (!animalQuestion) return; if (animalId === animalQuestion.id) { const text = `Дұрыс! Бұл ${animalQuestion.name}.`; setAnimalMessage(text + ' ⭐'); speakIfEnabled(text); addProgress({ gamesDelta: 1, starsDelta: 1 }); setTimeout(setupAnimalRound, 800); } else { const text = 'Сәл қателестік. Қайта байқап көрейік!'; setAnimalMessage(text); speakIfEnabled(text); } }; // Көңіл-күй const handleMoodSelect = (option: MoodOption) => { const today = getTodayKey(); if (moodToday === option.id) { const text = `${option.emoji} Көңіл-күйің қазірдің өзінде "${option.label}".`; setMoodMessage(text); speakIfEnabled(text); } else { setMoodToday(option.id); const text = `${option.emoji} Бүгінгі көңіл-күйің: ${option.label}. Жарайсың!`; setMoodMessage(text + ' ⭐'); speakIfEnabled(text); addProgress({ starsDelta: 1 }); completeMission('chooseMood'); AsyncStorage.setItem( 'aiKidsMood', JSON.stringify({ date: today, moodId: option.id }), ).catch((err) => console.log('Save mood error', err)); } }; // Хикая const handleStoryChoice = (choice: StoryChoice) => { if (choice.starsDelta) { addProgress({ starsDelta: choice.starsDelta }); } if (choice.nextId && STORY_NODES[choice.nextId]) { const nextNode = STORY_NODES[choice.nextId]; setStoryNodeId(choice.nextId); setStoryEnded(!!nextNode.isEnd); if (nextNode.isEnd) { completeMission('storyPlay'); } } else { setStoryEnded(true); completeMission('storyPlay'); } }; const toggleBreathing = () => { setBreathingPlaying((prev) => !prev); }; // Экраны const renderAuth = () => ( <Screen screenKey="auth"> <FadeInView> <Text style={styles.appTitle}>AER019 Kids Garage</Text> <Text style={styles.appSubtitle}> Балаларға арналған ойын-форматтағы AER019 Detailing әлемі </Text> </FadeInView> <FadeInView delay={80}> <BouncyCharacters /> </FadeInView> <FadeInView delay={160}> <WelcomeCarousel /> </FadeInView> <View style={styles.authSwitchRow}> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'login' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('login'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'login' && styles.authSwitchTextActive, ]} > Кіру </Text> </TouchableOpacity> <TouchableOpacity style={[ styles.authSwitchButton, authMode === 'register' && styles.authSwitchButtonActive, ]} onPress={() => { setAuthMode('register'); setAuthStep('credentials'); }} > <Text style={[ styles.authSwitchText, authMode === 'register' && styles.authSwitchTextActive, ]} > Тіркелу </Text> </TouchableOpacity> </View> <Card> {authMode === 'register' && ( <> <Text style={styles.sectionTitle}>Тіркелу</Text> <TextInput placeholder="Бала аты" style={styles.input} value={childName} onChangeText={setChildName} /> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <TextInput placeholder="Қауіпсіздік сөзі (3FA үшін)" style={styles.input} value={secretWord} onChangeText={setSecretWord} /> <PrimaryButton title="Тіркелу" onPress={handleRegister} /> <Text style={styles.helperText}> Қауіпсіздік сөзі – 3-ші фактор (3FA) ретінде қолданылады. </Text> </> )} {authMode === 'login' && ( <> {authStep === 'credentials' && ( <> <Text style={styles.sectionTitle}>Кіру (1-фактор)</Text> <TextInput placeholder="E-mail" style={styles.input} value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" /> <TextInput placeholder="Құпия сөз" style={styles.input} value={password} onChangeText={setPassword} secureTextEntry /> <PrimaryButton title="Кіру" onPress={handleLoginCredentials} /> <Text style={styles.helperText}>1-фактор — логин және құпия сөз.</Text> </> )} {authStep === '2fa' && ( <> <Text style={styles.sectionTitle}>2FA – код енгізу</Text> <TextInput placeholder="SMS / E-mail код" style={styles.input} value={twoFaCode} onChangeText={setTwoFaCode} keyboardType="numeric" /> <PrimaryButton title="Кодты растау" onPress={handleCheck2FA} /> <Text style={styles.helperText}> 2-фактор — бір реттік код. Бұл демо режим: код Alert арқылы көрсетілді. </Text> </> )} {authStep === '3fa' && ( <> <Text style={styles.sectionTitle}>3FA – биометрия немесе құпия сөз</Text> <TextInput placeholder="Қауіпсіздік сөзі (егер биометрия жоқ болса)" style={styles.input} value={secretWordLogin} onChangeText={setSecretWordLogin} /> <PrimaryButton title="3FA тексеру" onPress={handleThreeFA} /> <Text style={styles.helperText}> 3-фактор — биометрия (Face ID / Fingerprint) немесе алдын ала сақталған қауіпсіздік сөзі. </Text> </> )} </> )} </Card> </Screen> ); const renderHome = () => { const currentLevel = Math.floor(progress.stars / 5) + 1; const starsInLevel = progress.stars % 5; const remaining = starsInLevel === 0 ? 5 : 5 - starsInLevel; const activeCharacter = CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0]; const today = getTodayKey(); return ( <Screen screenKey="home"> <View style={styles.headerRow}> <View style={{ flex: 1 }}> <Text style={styles.homeTitle}> Сәлем, {savedUser?.childName || 'досым'}! 👋 </Text> <Text style={styles.homeSubtitleSmall}> Жұлдыздар: {progress.stars} | Деңгей: {currentLevel} </Text> <Text style={styles.homeSubtitleSmall}> Бүгін: {today} | Сенің AER019 досың: {activeCharacter.emoji}{' '} {activeCharacter.label} </Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.homeSubtitleSmall}> Келесі деңгейге дейін: {remaining} жұлдыз </Text> </View> <SecondaryButton title="Шығу" onPress={handleLogout} /> </View> <BouncyCharacters small /> <Text style={styles.appSubtitle}> AER019 Бот пен достары сені гаражда күтіп тұр! Қай модульге барамыз? 🌌 </Text> <Card> <Text style={styles.sectionTitle}>📚 Оқу мен білім</Text> <PrimaryButton title="🎧 Дауыспен оқу" onPress={() => goToScreen('reading')} /> <PrimaryButton title="🎮 Математикалық ойын" onPress={() => goToScreen('games')} /> <PrimaryButton title="📝 Тест" onPress={() => goToScreen('quiz')} /> <PrimaryButton title="🔤 Қазақ әліпбиі" onPress={() => goToScreen('alphabet')} /> <PrimaryButton title="🎨 Түстер" onPress={() => goToScreen('colors')} /> </Card> <Card> <Text style={styles.sectionTitle}>🎡 Қызық аймақ</Text> <PrimaryButton title="🎭 Көңіл-күй күнделігі" onPress={() => goToScreen('mood')} /> <PrimaryButton title="🌬️ Тыныс алу ойыны" onPress={() => goToScreen('calm')} /> <PrimaryButton title="📚 Таңдау хикаясы" onPress={() => goToScreen('story')} /> <PrimaryButton title="🐾 Жануарларды таны" onPress={() => goToScreen('animals')} /> <PrimaryButton title="📷 Сурет тану (AI)" onPress={() => goToScreen('image')} /> </Card> <Card> <Text style={styles.sectionTitle}>🏁 AER019 Detailing</Text> <Text style={styles.helperText}> Мұнда ата-аналар үшін нақты ақпарат: мекенжай, жұмыс уақыты, байланыс және т.б. </Text> <PrimaryButton title="ℹ️ О центре AER019" onPress={() => goToScreen('aboutCenter')} /> </Card> <Card> <Text style={styles.sectionTitle}>⭐ Прогресс және миссиялар</Text> <Text style={styles.progressText}>Ойындар: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жиналған жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}>Деңгей: {currentLevel}-деңгей 🌈</Text> <View style={{ height: 8 }} /> <PrimaryButton title="🎯 Күнделікті миссиялар" onPress={() => goToScreen('missions')} /> <PrimaryButton title="🏆 Жетістіктер" onPress={() => goToScreen('achievements')} /> <PrimaryButton title="⚙️ Баптаулар" onPress={() => goToScreen('settings')} /> </Card> </Screen> ); }; const renderReading = () => ( <Screen screenKey="reading"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎧 Дауыспен оқу</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>AER019 туралы мәтін</Text> <Text style={styles.readingText}>{READING_TEXT}</Text> <PrimaryButton title="Дауыспен оқу" onPress={handleSpeakReading} /> <SecondaryButton title="Тоқтату" onPress={handleStopSpeak} light /> <Text style={styles.helperText}> Бұл жерде AI-дың дауыс синтезі (Text-to-Speech) қолданылады. </Text> </Card> </Screen> ); const renderGames = () => ( <Screen screenKey="games"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎮 Математикалық ойын</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Есептер – гараждағы логика</Text> <Text style={styles.bigQuestion}> {gameA} {gameOp} {gameB} = ? </Text> <View style={styles.optionsRow}> {gameOptions.map((opt) => ( <TouchableOpacity key={opt} style={styles.optionButton} onPress={() => handleGameAnswer(opt)} > <Text style={styles.optionText}>{opt}</Text> </TouchableOpacity> ))} </View> {gameMessage !== '' && <Text style={styles.gameMessage}>{gameMessage}</Text>} </Card> </Screen> ); const renderQuiz = () => { const currentQ = QUIZ_DATA[quizIndex]; return ( <Screen screenKey="quiz"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📝 Тест (детейлинг)</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> {!quizFinished ? ( <> <Text style={styles.sectionTitle}> Сұрақ {quizIndex + 1} / {QUIZ_DATA.length} </Text> <Text style={styles.quizQuestion}>{currentQ.text}</Text> {currentQ.options.map((opt, idx) => ( <TouchableOpacity key={idx} style={styles.optionButtonWide} onPress={() => handleQuizAnswer(idx)} > <Text style={styles.optionText}>{opt}</Text> </TouchableOpacity> ))} </> ) : ( <> <Text style={styles.sectionTitle}>Нәтиже</Text> <Text style={styles.quizResultText}> Дұрыс жауаптар: {quizCorrect} / {QUIZ_DATA.length} </Text> <Text style={styles.helperText}> Жарайсың! Сенің жұлдыздарың прогресске қосылды. </Text> <PrimaryButton title="Қайта тапсыру" onPress={resetQuiz} /> </> )} </Card> </Screen> ); }; const renderProgress = () => { const currentLevel = Math.floor(progress.stars / 5) + 1; return ( <Screen screenKey="progress"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⭐ Прогресс</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> <Text style={styles.progressText}>Ойындар саны: {progress.gamesPlayed}</Text> <Text style={styles.progressText}>Тесттер саны: {progress.testsPassed}</Text> <Text style={styles.progressText}>Жұлдыздар: {progress.stars}</Text> <Text style={styles.progressText}>Деңгей: {currentLevel}-деңгей 🌟</Text> <LevelProgressBar stars={progress.stars} /> <Text style={styles.helperText}> Барлық деректер құрылғыда сақталады. Көп жеңіс – көп деңгей! </Text> </Card> </Screen> ); }; const renderImageAI = () => ( <Screen screenKey="image"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📷 AI – сурет тану</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Камера арқылы объект тану</Text> <Text style={styles.helperText}> Камераны ашып, бір затты түсір. AER019 Бот сенімен бірге не екенін болжайды (демо режим, шынайы модельмен ауыстыруға болады). </Text> <PrimaryButton title="Камераны ашу" onPress={openCamera} /> {aiLoading && ( <View style={styles.loadingRow}> <ActivityIndicator size="large" /> <Text style={styles.helperText}>AI суретті талдап жатыр...</Text> </View> )} {aiResult !== '' && <Text style={styles.aiResultText}>{aiResult}</Text>} </Card> {cameraOpen && ( <Card> <Text style={styles.sectionTitle}>Камера</Text> <View style={styles.cameraBox}> <CameraView style={styles.camera} ref={cameraRef} facing="back" /> </View> <PrimaryButton title="Суретке түсіру" onPress={handleTakePicture} /> </Card> )} </Screen> ); const renderAnimals = () => ( <Screen screenKey="animals"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🐾 Жануарларды таны</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Бұл қандай жануар?</Text> <View style={styles.animalEmojiBox}> <Text style={styles.animalEmoji}>{animalQuestion?.emoji}</Text> </View> <Text style={styles.helperText}>Төмендегі сөздердің ішінен дұрыс жануарды таңда.</Text> <View style={styles.optionsRow}> {animalOptions.map((animal) => ( <TouchableOpacity key={animal.id} style={styles.optionButton} onPress={() => handleAnimalAnswer(animal.id)} > <Text style={styles.optionText}>{animal.name}</Text> </TouchableOpacity> ))} </View> {animalMessage !== '' && <Text style={styles.gameMessage}>{animalMessage}</Text>} </Card> </Screen> ); const renderAlphabet = () => ( <Screen screenKey="alphabet"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🔤 Қазақ әліпбиі (гараж алфавиті)</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Әріпті бас – дауыстап айтсын</Text> <Text style={styles.helperText}> Әр түймені басқанда, әріп пен мысал сөз қазақ тілінде дауыстап айтылады. </Text> <View style={styles.alphabetGrid}> {KAZAKH_ALPHABET.map((item) => ( <TouchableOpacity key={item.letter} style={styles.alphabetButton} onPress={() => speakIfEnabled(`${item.letter}${item.word}`)} > <Text style={styles.alphabetLetter}>{item.letter}</Text> <Text style={styles.alphabetWord}>{item.word}</Text> </TouchableOpacity> ))} </View> </Card> </Screen> ); const renderColors = () => ( <Screen screenKey="colors"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎨 Түстер және авто</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Түстерді үйренейік</Text> <Text style={styles.helperText}> Әр түсті басқанда, оның атауы мен қысқа сипаттамасы дауыстап айтылады. </Text> <View style={styles.colorsGrid}> {COLOR_LESSONS.map((color) => ( <TouchableOpacity key={color.id} style={[styles.colorCard, { backgroundColor: color.bg }]} onPress={() => speakIfEnabled(`${color.name}. ${color.text}`)} > <Text style={styles.colorName}>{color.name}</Text> <Text style={styles.colorText}>{color.text}</Text> </TouchableOpacity> ))} </View> </Card> </Screen> ); const renderMissions = () => ( <Screen screenKey="missions"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎯 Күнделікті миссиялар</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}> Бүгінгі тапсырмалар ({missions.date || getTodayKey()}) </Text> {missions.items && missions.items.length > 0 ? ( missions.items.map((m) => ( <View key={m.id} style={styles.missionRow}> <Text style={styles.missionIcon}>{m.done ? '⭐' : '⬜️'}</Text> <Text style={[styles.missionText, m.done && styles.missionTextDone]}> {m.title} </Text> </View> )) ) : ( <Text style={styles.helperText}> Миссиялар жүктелуде немесе бүгінге арналған тапсырмалар жоқ. </Text> )} <PrimaryButton title="Сыйлықты алу" onPress={collectMissionReward} /> <Text style={styles.helperText}> Барлық миссияларды орындасаң, қосымша жұлдыздар беріледі. Миссиялар әр күн сайын жаңарады. </Text> </Card> </Screen> ); const renderAchievements = () => ( <Screen screenKey="achievements"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🏆 Жетістіктер</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Сенің жетістіктерің</Text> {ACHIEVEMENTS.map((ach) => { const value = ach.type === 'gamesPlayed' ? progress.gamesPlayed : ach.type === 'testsPassed' ? progress.testsPassed : progress.stars; const unlocked = value >= ach.threshold; return ( <View key={ach.id} style={styles.achievementRow}> <Text style={styles.achievementIcon}>{unlocked ? '✅' : '🔒'}</Text> <View style={{ flex: 1 }}> <Text style={styles.achievementTitle}>{ach.title}</Text> <Text style={styles.achievementDesc}> {ach.description} ({Math.min(value, ach.threshold)}/{ach.threshold}) </Text> </View> </View> ); })} <Text style={styles.helperText}> Жұлдыздар, ойындар және тесттер саны көбейген сайын жаңа жетістіктер ашылады. </Text> </Card> </Screen> ); const renderSettings = () => { const activeCharacter = CHARACTERS.find((ch) => ch.id === settings.chosenCharacterId) || CHARACTERS[0]; return ( <Screen screenKey="settings"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>⚙️ Баптаулар</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Дыбыс</Text> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Дауыстық көмек</Text> <Switch value={settings.voiceHints} onValueChange={(value) => updateSettings({ voiceHints: value })} /> </View> <Text style={styles.helperText}> Егер өшірсең, қолданба ештеңе сөйлемейді (мәтін де, кеңестер де). </Text> </Card> <Card> <Text style={styles.sectionTitle}>Математика деңгейі</Text> <View style={styles.difficultyRow}> {[ { id: 'easy', label: 'Жеңіл' }, { id: 'medium', label: 'Орта' }, { id: 'hard', label: 'Қиын' }, ].map((lvl) => ( <TouchableOpacity key={lvl.id} style={[ styles.difficultyButton, settings.mathLevel === (lvl.id as MathLevel) && styles.difficultyButtonActive, ]} onPress={() => updateSettings({ mathLevel: lvl.id as MathLevel })} > <Text style={[ styles.difficultyText, settings.mathLevel === (lvl.id as MathLevel) && styles.difficultyTextActive, ]} > {lvl.label} </Text> </TouchableOpacity> ))} </View> <Text style={styles.helperText}> Деңгей жоғарылаған сайын сандар да үлкенірек болады, кейде азайту амалы да қосылады. </Text> </Card> <Card> <Text style={styles.sectionTitle}>Кейіпкер таңдау</Text> <Text style={styles.helperText}> Таңдалған кейіпкер басты экранда аватар ретінде көрсетіледі, ал бүкіл қолданбаның түс схемасы соған сай өзгереді. </Text> <View style={styles.charactersRow}> {CHARACTERS.map((ch) => { const isActive = ch.id === settings.chosenCharacterId; return ( <TouchableOpacity key={ch.id} style={[styles.characterBubble, isActive && styles.characterBubbleActive]} onPress={() => updateSettings({ chosenCharacterId: ch.id })} > <Text style={styles.characterEmoji}>{ch.emoji}</Text> <Text style={styles.characterLabel}>{ch.label}</Text> </TouchableOpacity> ); })} </View> <View style={styles.settingsRow}> <Text style={styles.settingsLabel}>Таңдалған кейіпкер</Text> <Text style={styles.settingsValue}> {activeCharacter.emoji} {activeCharacter.label} </Text> </View> </Card> </Screen> ); }; const renderMood = () => ( <Screen screenKey="mood"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🎭 Көңіл-күй күнделігі</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Бүгін өзіңді қалай сезініп тұрсың?</Text> <Text style={styles.helperText}> Бір эмоцияны таңда. AER019 Бот сенің көңіл-күйіңді еске сақтайды. </Text> <View style={styles.moodGrid}> {MOOD_OPTIONS.map((m) => { const active = moodToday === m.id; return ( <TouchableOpacity key={m.id} style={[ styles.moodCard, { backgroundColor: m.color }, active && styles.moodCardActive, ]} onPress={() => handleMoodSelect(m)} > <Text style={styles.moodEmoji}>{m.emoji}</Text> <Text style={styles.moodLabel}>{m.label}</Text> </TouchableOpacity> ); })} </View> {moodMessage !== '' && <Text style={styles.gameMessage}>{moodMessage}</Text>} </Card> </Screen> ); const renderCalm = () => ( <Screen screenKey="calm"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>🌬️ Тыныс алу ойыны</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>Cпокой дем ал, дем шығар</Text> <Text style={styles.helperText}> Шеңбер үлкейгенде – дем ал, кішірейгенде – дем шығар. Бірнеше рет қайтала. </Text> <BreathingCircle playing={breathingPlaying} /> <Text style={styles.breathingLabel}>{breathingLabel}</Text> <PrimaryButton title={breathingPlaying ? 'Тоқтату' : 'Бастау'} onPress={toggleBreathing} /> {breathingMessage !== '' && ( <Text style={styles.gameMessage}>{breathingMessage}</Text> )} </Card> </Screen> ); const renderStory = () => { const node = STORY_NODES[storyNodeId] || STORY_NODES.start; return ( <Screen screenKey="story"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>📚 Таңдау хикаясы</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <Text style={styles.sectionTitle}>{node.title}</Text> <Text style={styles.readingText}>{node.text}</Text> {!node.isEnd && node.choices && node.choices.length > 0 && ( <> {node.choices.map((choice) => ( <TouchableOpacity key={choice.id} style={styles.optionButtonWide} onPress={() => handleStoryChoice(choice)} > <Text style={styles.optionText}>{choice.text}</Text> </TouchableOpacity> ))} </> )} {node.isEnd && ( <> <Text style={styles.helperText}> Хикая аяқталды. Таңдауларың үшін рахмет! Тағы да ойнағың келсе – басынан бастауға болады. </Text> <PrimaryButton title="Қайта бастау" onPress={resetStory} /> </> )} </Card> </Screen> ); }; const renderAboutCenter = () => ( <Screen screenKey="about"> <View style={styles.headerRow}> <Text style={styles.homeTitle}>ℹ️ О центре AER019</Text> <SecondaryButton title="Артқа" onPress={() => goToScreen('home')} /> </View> <Card> <View style={{ alignItems: 'center', marginBottom: 12 }}> <Image source={require('./assets/aer019-logo.png')} style={styles.centerLogo} resizeMode="contain" /> </View> <Text style={styles.sectionTitle}>{CENTER_INFO.name}</Text> <Text style={styles.centerTagline}>{CENTER_INFO.slogan}</Text> </Card> <Card> <Text style={styles.sectionTitle}>📍 Адрес</Text> <Text style={styles.centerInfoLine}>{CENTER_INFO.address}</Text> <Text style={[styles.sectionTitle, { marginTop: 16 }]}>🕒 График работы</Text> <Text style={styles.centerInfoLine}>{CENTER_INFO.workTime}</Text> <Text style={[styles.sectionTitle, { marginTop: 16 }]}>📞 Контакты</Text> <Text style={styles.centerInfoLine}> Телефон: <Text style={styles.centerInfoLabel}>{CENTER_INFO.phone}</Text> </Text> <Text style={styles.centerInfoLine}> Instagram: <Text style={styles.centerInfoLabel}>{CENTER_INFO.instagramHandle}</Text> </Text> <View style={styles.contactButtonsRow}> <SecondaryButton title="Позвонить" onPress={() => Linking.openURL(`tel:${CENTER_INFO.phone.replace(/[^+\d]/g, '')}`) } /> <SecondaryButton title="Открыть Instagram" onPress={() => Linking.openURL(CENTER_INFO.instagramUrl)} /> </View> <Text style={styles.helperText}> Здесь можно вставить реальные контакты, акции, описание услуг и т.д. </Text> </Card> </Screen> ); // Итоговый render return ( <ThemeContext.Provider value={themeValue}> <SafeAreaView style={styles.safe}> {!isLoggedIn ? renderAuth() : currentScreen === 'home' ? renderHome() : currentScreen === 'reading' ? renderReading() : currentScreen === 'games' ? renderGames() : currentScreen === 'quiz' ? renderQuiz() : currentScreen === 'progress' ? renderProgress() : currentScreen === 'image' ? renderImageAI() : currentScreen === 'animals' ? renderAnimals() : currentScreen === 'alphabet' ? renderAlphabet() : currentScreen === 'colors' ? renderColors() : currentScreen === 'missions' ? renderMissions() : currentScreen === 'achievements' ? renderAchievements() : currentScreen === 'settings' ? renderSettings() : currentScreen === 'mood' ? renderMood() : currentScreen === 'calm' ? renderCalm() : currentScreen === 'story' ? renderStory() : currentScreen === 'aboutCenter' ? renderAboutCenter() : renderHome()} </SafeAreaView> </ThemeContext.Provider> ); }; export default App; // ============================= // Стили (AER019 неон-гараж) // ============================= const styles = StyleSheet.create({ safe: { flex: 1, backgroundColor: '#000', }, gradient: { flex: 1, }, scroll: { padding: 20, paddingBottom: 40, }, appTitle: { fontSize: 30, fontWeight: '800', color: '#fff', textAlign: 'center', marginBottom: 8, letterSpacing: 0.5, }, appSubtitle: { fontSize: 16, color: 'rgba(255,255,255,0.9)', textAlign: 'center', marginBottom: 16, }, homeTitle: { fontSize: 22, fontWeight: '700', color: '#fff', }, homeSubtitleSmall: { fontSize: 14, color: 'rgba(255,255,255,0.9)', marginTop: 4, }, headerRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, gap: 8, justifyContent: 'space-between', }, card: { backgroundColor: 'rgba(255,255,255,0.96)', borderRadius: 22, padding: 18, marginBottom: 16, shadowColor: '#000', shadowOpacity: 0.18, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 5, }, sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 10, color: '#222', }, primaryButton: { backgroundColor: '#00E0FF', paddingVertical: 13, borderRadius: 18, alignItems: 'center', marginTop: 8, }, primaryButtonText: { color: '#020617', fontSize: 15, fontWeight: '700', }, secondaryButton: { borderColor: 'rgba(255,255,255,0.9)', borderWidth: 1, paddingVertical: 8, paddingHorizontal: 12, borderRadius: 20, }, secondaryButtonLight: { borderColor: '#ccc', backgroundColor: '#fff', }, secondaryButtonText: { color: '#fff', fontSize: 14, fontWeight: '600', }, secondaryButtonTextDark: { color: '#333', }, input: { backgroundColor: '#0F172A', borderRadius: 14, paddingHorizontal: 14, paddingVertical: 9, marginBottom: 10, fontSize: 14, borderWidth: 1, borderColor: '#1E293B', color: '#E5E7EB', }, helperText: { fontSize: 12, color: '#777', marginTop: 8, }, authSwitchRow: { flexDirection: 'row', backgroundColor: 'rgba(15,23,42,0.7)', borderRadius: 30, padding: 4, marginBottom: 16, alignSelf: 'center', }, authSwitchButton: { flex: 1, paddingVertical: 8, borderRadius: 26, alignItems: 'center', }, authSwitchButtonActive: { backgroundColor: 'rgba(255,255,255,0.95)', }, authSwitchText: { color: 'rgba(255,255,255,0.9)', fontWeight: '600', }, authSwitchTextActive: { color: '#111827', }, readingText: { fontSize: 16, lineHeight: 22, color: '#444', marginBottom: 12, }, bigQuestion: { fontSize: 30, fontWeight: '800', textAlign: 'center', marginBottom: 12, color: '#111827', }, optionsRow: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8, }, optionButton: { backgroundColor: '#E0F2FE', paddingVertical: 10, paddingHorizontal: 14, borderRadius: 14, marginTop: 8, flexBasis: '48%', alignItems: 'center', }, optionButtonWide: { backgroundColor: '#E0F2FE', paddingVertical: 10, paddingHorizontal: 14, borderRadius: 14, marginTop: 8, }, optionText: { fontSize: 14, fontWeight: '600', color: '#0F172A', }, gameMessage: { marginTop: 12, fontSize: 15, textAlign: 'center', color: '#111827', }, quizQuestion: { fontSize: 16, marginBottom: 8, color: '#111827', }, quizResultText: { fontSize: 18, fontWeight: '700', marginBottom: 8, color: '#111827', }, progressText: { fontSize: 15, marginBottom: 4, color: '#111827', }, cameraBox: { borderRadius: 20, overflow: 'hidden', height: 220, marginBottom: 12, }, camera: { flex: 1, }, loadingRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 12, }, aiResultText: { marginTop: 10, fontSize: 15, fontWeight: '600', color: '#111827', }, animalEmojiBox: { alignItems: 'center', marginBottom: 12, }, animalEmoji: { fontSize: 86, }, alphabetGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8, marginTop: 12, }, alphabetButton: { width: '23%', backgroundColor: '#E0F2FE', borderRadius: 12, paddingVertical: 10, paddingHorizontal: 4, alignItems: 'center', }, alphabetLetter: { fontSize: 20, fontWeight: '800', color: '#0369A1', }, alphabetWord: { fontSize: 11, color: '#334155', marginTop: 4, textAlign: 'center', }, colorsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 12, }, colorCard: { flexBasis: '48%', borderRadius: 18, paddingVertical: 14, paddingHorizontal: 10, }, colorName: { fontSize: 18, fontWeight: '800', color: '#0B1120', marginBottom: 6, }, colorText: { fontSize: 13, color: 'rgba(15,23,42,0.9)', }, // Кейіпкерлер charactersRow: { flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end', marginBottom: 12, gap: 10, flexWrap: 'wrap', }, characterBubble: { paddingVertical: 10, paddingHorizontal: 12, borderRadius: 999, alignItems: 'center', justifyContent: 'center', minWidth: 90, shadowColor: '#000', shadowOpacity: 0.35, shadowRadius: 10, shadowOffset: { width: 0, height: 4 }, elevation: 6, }, characterBubbleSmall: { paddingVertical: 6, paddingHorizontal: 10, minWidth: 70, }, characterBubbleActive: { borderWidth: 2, borderColor: '#FACC15', }, characterEmoji: { fontSize: 26, }, characterLabel: { marginTop: 4, fontSize: 11, fontWeight: '700', color: '#0B1120', }, // Welcome slider carouselContainer: { height: 190, marginBottom: 12, }, carouselCard: { flex: 1, marginHorizontal: 12, borderRadius: 20, paddingVertical: 16, paddingHorizontal: 12, alignItems: 'center', justifyContent: 'center', }, carouselEmoji: { fontSize: 48, marginBottom: 4, }, carouselTitle: { fontSize: 20, fontWeight: '800', color: '#fff', marginTop: 4, }, carouselText: { fontSize: 13, color: 'rgba(255,255,255,0.95)', textAlign: 'center', marginTop: 6, }, carouselDots: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginTop: 8, }, carouselDot: { height: 8, borderRadius: 4, backgroundColor: 'rgba(255,255,255,0.9)', marginHorizontal: 4, }, // Level progress bar levelBarOuter: { height: 10, borderRadius: 999, backgroundColor: 'rgba(15,23,42,0.7)', overflow: 'hidden', marginTop: 6, }, levelBarInner: { height: '100%', borderRadius: 999, backgroundColor: '#FFD54F', }, // Daily missions missionRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, gap: 8, }, missionIcon: { fontSize: 18, }, missionText: { fontSize: 14, color: '#111827', }, missionTextDone: { textDecorationLine: 'line-through', color: '#9CA3AF', }, // Achievements achievementRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, gap: 10, }, achievementIcon: { fontSize: 20, }, achievementTitle: { fontSize: 15, fontWeight: '700', color: '#111827', }, achievementDesc: { fontSize: 12, color: '#6B7280', marginTop: 2, }, // Settings settingsRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 8, }, settingsLabel: { fontSize: 14, color: '#111827', fontWeight: '600', }, settingsValue: { fontSize: 14, color: '#374151', }, difficultyRow: { flexDirection: 'row', gap: 8, marginTop: 8, }, difficultyButton: { flex: 1, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: '#1F2937', alignItems: 'center', backgroundColor: '#0F172A', }, difficultyButtonActive: { backgroundColor: '#00E0FF', borderColor: '#00E0FF', }, difficultyText: { fontSize: 13, color: '#E5E7EB', fontWeight: '600', }, difficultyTextActive: { color: '#020617', }, // Mood moodGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginTop: 12, }, moodCard: { flexBasis: '48%', borderRadius: 18, paddingVertical: 18, paddingHorizontal: 10, alignItems: 'center', }, moodCardActive: { borderWidth: 2, borderColor: '#FACC15', }, moodEmoji: { fontSize: 30, }, moodLabel: { marginTop: 4, fontSize: 14, fontWeight: '700', color: '#111827', }, // Breathing breathingWrapper: { alignItems: 'center', marginVertical: 16, }, breathingCircle: { width: 140, height: 140, borderRadius: 70, borderWidth: 4, borderColor: 'rgba(148,163,184,0.8)', backgroundColor: 'rgba(15,23,42,0.7)', }, breathingLabel: { fontSize: 18, fontWeight: '700', textAlign: 'center', marginBottom: 8, color: '#111827', }, // О центре centerLogo: { width: 160, height: 80, marginBottom: 6, }, centerTagline: { fontSize: 14, color: '#4B5563', marginTop: 4, }, centerInfoLine: { fontSize: 14, color: '#111827', marginTop: 4, }, centerInfoLabel: { fontWeight: '700', }, contactButtonsRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 8, marginTop: 12, }, });

Что ещё можно улучшить дальше

Код уже мощный, но можно шаг за шагом довести до “продакшн” уровня:

  1. Разбить на модули

    • screens/ (Home, Auth, Games, Quiz, AboutCenter и т.д.)
    • components/ (Buttons, Card, Screen, BouncyCharacters, LevelProgressBar)
    • data/ (QUIZ_DATA, ALPHABET, STORY_NODES, CENTER_INFO)
    • theme/ (THEMES, ThemeContext)
  2. Добавить React Navigation

    • Вместо большого тернарника по currentScreen использовать @react-navigation/native + createNativeStackNavigator.
    • Будет легче добавлять новые экраны и переходы.
  3. Локализация

    • Вынести строки в i18n (например, react-i18next или expo-localization + свой словарь).
    • Сделать переключатель “Қазақша / Русский”.
  4. Реальная запись на детейлинг

    • Экран “Запись”: имя, телефон, авто, желаемое время.
    • Отправка через Linking.openURL('https://wa.me/...') или HTTP-запросом на твой backend.
  5. Валидация и UX

    • Более мягкие сообщения об ошибках (под инпутами).
    • Блокировка кнопки, пока не заполнены обязательные поля.
  6. Аналитика / метрики

    • Сколько детей доходят до миссий, какие игры запускают чаще.
    • Можно потом оптимизировать UX и контент.

Если хочешь, дальше могу помочь разбить этот монолит на файлы или сделать экран записи на детейлинг с отправкой в WhatsApp/Telegram.

Share this Q&A