import AsyncStorage from '@react-native-async-stor...
Tạo vào: 8 tháng 12, 2025
Trả lời bằng GPT-5.1 Thinking bởi Chat01
Tạo vào: 8 tháng 12, 2025
Trả lời bằng GPT-5.1 Thinking bởi Chat01
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 );,
);
texttx.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));
textif (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);
texttimeoutId = 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);
textif (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
};
textlet 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];
textfor (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);
}
textif (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;
}
textif (!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();
textreturn ( <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];
textreturn ( <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;
textreturn ( <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];
textreturn ( <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;
textreturn ( <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
Сейчас у тебя тема «детская / мультики» (градиенты в THEMES + дефолтные цвета в ThemeContext).
Сделаем тёмную «детейлинг-центр AER019» тему: тёмный фон, неоновый бирюзовый акцент, жёлтый прогресс.
Ниже — конкретные куски кода, которые можно просто заменить.
ThemeContext (глобальные цвета по умолчанию)Найди в самом начале файла:
jsconst ThemeContext = createContext({ primaryColor: '#ff7a59', levelColor: '#ffe66d', getScreenColors: () => ['#0f2027', '#203a43', '#2c5364'], });
Замени на:
jsconst ThemeContext = createContext({ // Основной бренд-цвет AER019 (неоновый) primaryColor: '#00E0FF', // Цвет уровня / прогресса (тёплый жёлтый, как подсветка) levelColor: '#FFD54F', // Общий фон – тёмный, «гараж/детейлинг» getScreenColors: () => ['#020617', '#050816', '#0B1120'], });
THEMES.robot на AER019Найди объект THEMES = { ... } и полностью замени его на этот:
jsconst 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'], }, }, };
Теперь все экраны будут на тёмном фоне с неоновыми акцентами — в стиле детейлинг-центра.
Если хочешь, чтобы и подписи под аватарками были про детейлинг, можно чуть поменять CHARACTERS:
jsconst 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 Learning → AER019 Detailing Kids или что-то своё).
да давай дорабатываем мой проект
ты напиши полный новый код с той же логикой как в коде для детейлинга
Кидаю полностью переписанный App.js с тем же функционалом, но под бренд детейлинг-центра AER019: тёмная гаражная тема, неон, машины и т.п. Логика, стейты, экраны и навигация — такие же, так что просто заменяешь свой файл на этот.
jsimport 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', }, });
Если хочешь, следующим шагом можем:
ты же пишешь для vscode?
и да давай переходить к следующему шагу
Да, всё, что я пишу — обычный JS/React Native-код, идеально подходит для VS Code 👍
Ты просто редактируешь App.js в своём Expo-проекте.
Давай сделаем следующий шаг: добавим логотип AER019 и экран “О центре / контакты” прямо в приложение.
project-root/assets/assets/aer019-logo.pngApp.jsВ самом верху, где импортируется react-native, добавь Image и Linking:
jsimport { ActivityIndicator, Alert, Animated, Easing, SafeAreaView, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, useWindowDimensions, View, Image, // ← ДОБАВИЛИ Linking, // ← ДОБАВИЛИ (для открытия телефона/инсты) } from 'react-native';
Чуть ниже констант (например, после 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', };
renderAboutCenterЧуть ниже других renderXxx-функций (например, после renderAchievements или renderSettings) добавь:
jsconst 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> );
В renderHome() найди место, где идут карточки с кнопками.
Добавим ещё одну карточку для AER019 (можешь вставить её после блока “⭐ Прогресс және миссиялар” или до него).
Например, сразу после блока 🎡 Қызық аймақ вставь:
js<Card> <Text style={styles.sectionTitle}>🏁 AER019 Detailing</Text> <Text style={styles.helperText}> Мұнда ата-аналар үшін нақты ақпарат: мекенжай, жұмыс уақыты, байланыс және т.б. </Text> <PrimaryButton title="ℹ️ О центре AER019" onPress={() => goToScreen('aboutCenter')} /> </Card>
В самом низу, в 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 там не нужен.
Внизу, в StyleSheet.create({ ... }), добавь к стилям:
jscenterLogo: { 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, },
После этого:
App.js в VS Code../assets/aer019-logo.png существует.npx expo start.Если всё ок — на главном экране появится новая карточка “AER019 Detailing”, а внутри — экран с логотипом и контактами.
Хочешь, следующим шагом можем:
а ты можешь все это написать для index.tsx одним кодом
так ты напишешь единый код
что еще можно улучшить
Да, давай сделаем единый 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-storagetsximport 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, }, });
Код уже мощный, но можно шаг за шагом довести до “продакшн” уровня:
Разбить на модули
screens/ (Home, Auth, Games, Quiz, AboutCenter и т.д.)components/ (Buttons, Card, Screen, BouncyCharacters, LevelProgressBar)data/ (QUIZ_DATA, ALPHABET, STORY_NODES, CENTER_INFO)theme/ (THEMES, ThemeContext)Добавить React Navigation
currentScreen использовать @react-navigation/native + createNativeStackNavigator.Локализация
i18n (например, react-i18next или expo-localization + свой словарь).Реальная запись на детейлинг
Linking.openURL('https://wa.me/...') или HTTP-запросом на твой backend.Валидация и UX
Аналитика / метрики
Если хочешь, дальше могу помочь разбить этот монолит на файлы или сделать экран записи на детейлинг с отправкой в WhatsApp/Telegram.