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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .expo/types/router.d.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ yarn-error.*
*.tsbuildinfo

app-example
.kimchi/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@

## 📈 История улучшений (Последние циклы)

* **v1.6.1 (Исправление типов и линтинга)**: Устранены все ошибки TypeScript (`tsc --noEmit`: 0 ошибок) и все ошибки ESLint (`npm run lint`: 0 ошибок, 102 предупреждения). Добавлены недостающие методы/типы в сервисы (`advancedMoodTracker`, `interactiveMeditation`, `personalization`, `notificationService`, `smartNotificationService`, `audioService`, `PsychologyService`), исправлены TS-ошибки в компонентах (`sounds`, `gamification`, `mini-games`, `ai-coach`, `enhanced-exercises`, `enhanced-settings`, `personalized-recommendations` и др.), заданы `displayName` для `React.memo`, добавлены Jest-глобали в `eslint.config.js`. Все тесты проходят: `npm test` — 5/5.
* **v1.6.0 (Цикл 6)**: Внедрены "Круги" в Сообщество, добавлен виджет аналитики настроения, база знаний расширена до 50+ статей, улучшена память AI-коуча.
* **v1.5.0 (Цикл 5)**: Добавлены "Ежедневные испытания", глубокие ссылки из чата на статьи, обновлен UI карточек сообщества.
* **v1.4.0 (Цикл 4)**: Реализована озвучка (TTS) для коуча, "Умный дневник", Lottie-анимации достижений.
Expand Down
1 change: 1 addition & 0 deletions __tests__/setup.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-env jest */
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';

jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
Expand Down
24 changes: 12 additions & 12 deletions app/(tabs)/advanced-analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { smartNotificationService } from '../../services/smartNotificationServic
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');

// Мемоизированные компоненты для производительности
const MemoizedInsightCard = React.memo(({ insight, onPress }: {
insight: PersonalInsight;
onPress: () => void;
}) => {
const MemoizedInsightCard = React.memo(function MemoizedInsightCard({ insight, onPress }: {
insight: PersonalInsight;
onPress: () => void;
}) {
const scaleValue = useSharedValue(1);
const opacityValue = useSharedValue(1);

Expand Down Expand Up @@ -101,10 +101,10 @@ const MemoizedInsightCard = React.memo(({ insight, onPress }: {
);
});

const MemoizedRiskIndicator = React.memo(({ riskLevel, factors }: {
const MemoizedRiskIndicator = React.memo(function MemoizedRiskIndicator({ riskLevel, factors }: {
riskLevel: string;
factors: string[];
}) => {
}) {
const getRiskColor = (level: string) => {
switch (level) {
case 'low': return '#4CAF50';
Expand Down Expand Up @@ -165,7 +165,7 @@ const MemoizedRiskIndicator = React.memo(({ riskLevel, factors }: {
export default function AdvancedAnalyticsPage() {
const insets = useSafeAreaInsets();
const { userProfile, progress, soberDays } = useRecovery();
const { moodData, getAnalytics } = useAnalytics();
const { moodEntries } = useAnalytics();

const [insights, setInsights] = useState<PersonalInsight[]>([]);
const [riskAssessment, setRiskAssessment] = useState<any>(null);
Expand Down Expand Up @@ -199,16 +199,16 @@ export default function AdvancedAnalyticsPage() {
setLoading(true);

// Анализируем паттерны пользователя
const userInsights = advancedInsightsService.analyzeUserPatterns(progress || [], moodData || []);
const userInsights = advancedInsightsService.analyzeUserPatterns(progress || [], moodEntries || []);
setInsights(userInsights);

// Оцениваем текущий риск
const currentRisk = advancedInsightsService.assessRelapsRisk({
mood: moodData?.[moodData.length - 1]?.mood || 3,
stress: moodData?.[moodData.length - 1]?.stressLevel || 3,
sleep: moodData?.[moodData.length - 1]?.sleepQuality || 3,
mood: moodEntries?.[moodEntries.length - 1]?.mood || 3,
stress: moodEntries?.[moodEntries.length - 1]?.stressLevel || 3,
sleep: moodEntries?.[moodEntries.length - 1]?.sleepQuality || 3,
socialSupport: 4, // заглушка
cravings: moodData?.[moodData.length - 1]?.cravingLevel || 2
cravings: moodEntries?.[moodEntries.length - 1]?.cravingLevel || 2
});
setRiskAssessment(currentRisk);

Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/advanced-therapy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function AdvancedTherapyPage() {
</ScrollView>

<AdvancedTherapyPlayer visible={showTherapyPlayer} therapy={selectedTherapy || undefined} onClose={() => setShowTherapyPlayer(false)} />
<TherapeuticSoundPlayer visible={showSoundPlayer} sound={selectedSound || undefined} onClose={() => setShowSoundPlayer(false)} />
<TherapeuticSoundPlayer sound={selectedSound || undefined} onClose={() => setShowSoundPlayer(false)} />
</View>
);
}
Expand Down
21 changes: 13 additions & 8 deletions app/(tabs)/ai-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ interface AIAssistantPageProps {
}

// Компонент сообщения в чате
const MessageBubble = React.memo(({ message, onSuggestionPress }: {
const MessageBubble = React.memo(function MessageBubble({ message, onSuggestionPress }: {
message: ChatMessage;
onSuggestionPress: (suggestion: ActionSuggestion) => void;
}) => {
}) {
const isUser = message.senderType === 'user';
const isEmergency = message.messageType === 'emergency';

Expand All @@ -81,7 +81,7 @@ const MessageBubble = React.memo(({ message, onSuggestionPress }: {
slideValue.value = withSpring(0, { damping: 15 });
}, []);

const getBubbleColor = () => {
const getBubbleColor = (): readonly [string, string] => {
if (isEmergency) return ['#FF5722', '#FF3D00'];
if (isUser) return ['#2E7D4A', '#1B5E20'];
return ['#FFFFFF', '#F1F8E9'];
Expand Down Expand Up @@ -161,10 +161,10 @@ const MessageBubble = React.memo(({ message, onSuggestionPress }: {
});

// Компонент быстрых ответов
const QuickResponses = React.memo(({ onSelect, currentMood }: {
const QuickResponses = React.memo(function QuickResponses({ onSelect, currentMood }: {
onSelect: (response: string) => void;
currentMood: number;
}) => {
}) {
const getQuickResponses = () => {
if (currentMood <= 2) {
return [
Expand Down Expand Up @@ -211,7 +211,7 @@ const QuickResponses = React.memo(({ onSelect, currentMood }: {
});

// Индикатор печати ИИ
const TypingIndicator = React.memo(() => {
const TypingIndicator = React.memo(function TypingIndicator() {
const dot1 = useSharedValue(0.3);
const dot2 = useSharedValue(0.3);
const dot3 = useSharedValue(0.3);
Expand Down Expand Up @@ -338,15 +338,20 @@ const AIAssistantPage: React.FC<AIAssistantPageProps> = ({ initialContext }) =>
timeOfDay: new Date().getHours() > 12 ? 'afternoon' : 'morning',
});

if (!aiResponse.success) {
setIsTyping(false);
return;
}

const responseMessage: ChatMessage = {
id: `ai_${Date.now()}`,
senderId: 'ai_coach',
senderType: 'ai',
content: aiResponse.message,
content: aiResponse.data.message,
timestamp: new Date(),
messageType: 'text',
metadata: {
suggestions: aiResponse.suggestions.map((s, i) => ({
suggestions: aiResponse.data.suggestions.map((s: string, i: number) => ({
id: `s_${i}`,
type: 'technique',
title: s,
Expand Down
4 changes: 3 additions & 1 deletion app/(tabs)/ai-coach.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ChallengeCard = React.memo(({ challenge, onComplete }: {
style={[styles.challengeCard, challenge.completed && styles.challengeCompleted]}
>
<View style={styles.challengeIconContainer}>
<MaterialIcons name={challenge.icon} size={24} color={challenge.completed ? "#FFF" : "#2E7D4A"} />
<MaterialIcons name={challenge.icon as keyof typeof MaterialIcons.glyphMap} size={24} color={challenge.completed ? "#FFF" : "#2E7D4A"} />
</View>
<View style={styles.challengeInfo}>
<Text style={[styles.challengeTitle, challenge.completed && styles.challengeCompletedText]}>
Expand All @@ -51,6 +51,7 @@ const ChallengeCard = React.memo(({ challenge, onComplete }: {
)}
</Animated.View>
));
ChallengeCard.displayName = 'ChallengeCard';

// Refactored Message component
const MessageBubble = React.memo(({ message, onArticlePress, onCoursePress, onSpeak, isSpeaking }: {
Expand Down Expand Up @@ -167,6 +168,7 @@ const MessageBubble = React.memo(({ message, onArticlePress, onCoursePress, onSp
</Animated.View>
);
});
MessageBubble.displayName = 'MessageBubble';

export default function EnhancedAICoach() {
const insets = useSafeAreaInsets();
Expand Down
8 changes: 4 additions & 4 deletions app/(tabs)/articles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ import { articlesDatabase } from '../../services/articlesDatabase';

const articles: Article[] = articlesDatabase;

const MemoizedArticleCard = React.memo(({ article, onPress, isFavorite, onToggleFavorite }: {
const MemoizedArticleCard = React.memo(function MemoizedArticleCard({ article, onPress, isFavorite, onToggleFavorite }: {
article: Article;
onPress: () => void;
isFavorite: boolean;
onToggleFavorite: (id: string) => void;
}) => {
}) {
const scaleValue = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scaleValue.value }]
Expand Down Expand Up @@ -104,12 +104,12 @@ const MemoizedArticleCard = React.memo(({ article, onPress, isFavorite, onToggle
);
});

const MemoizedFilterChip = React.memo(({ label, selected, onPress, count }: {
const MemoizedFilterChip = React.memo(function MemoizedFilterChip({ label, selected, onPress, count }: {
label: string;
selected: boolean;
onPress: () => void;
count: number;
}) => {
}) {
const scaleValue = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scaleValue.value }]
Expand Down
1 change: 1 addition & 0 deletions app/(tabs)/community.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FlatList, Image, Dimensions, Modal, TextInput, Alert
} from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import * as Haptics from 'expo-haptics';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import { CommunityService, SuccessStory, SupportPost, ExpertQA, ReactionType, CommunityGoal } from '../../services/communityService';
Expand Down
18 changes: 10 additions & 8 deletions app/(tabs)/enhanced-exercises.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ const { width: screenWidth } = Dimensions.get('window');

// Объединенная библиотека всех техник
const allTechniques = [
...advancedNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Базовые', color: '#FF9800' })),
...additionalNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Продвинутые', color: '#FF5722' })),
...beliefWorkTechniques.map(tech => ({ ...tech, category: 'Работа с убеждениями', color: '#E91E63' })),
...integrativeNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Интегративные', color: '#9C27B0' })),
...advancedCBTTechniques.map(tech => ({ ...tech, category: 'КПТ', color: '#2196F3' })),
...traumaInformedTechniques.map(tech => ({ ...tech, category: 'Работа с травмой', color: '#673AB7' })),
...integrativeTherapyTechniques.map(tech => ({ ...tech, category: 'Интегративные методы', color: '#4CAF50' })),
...allExpandedTechniques.map(tech => ({ ...tech, category: 'НЛП Расширенные', color: '#00BCD4' }))
...advancedNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Базовые', color: '#FF9800', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...additionalNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Продвинутые', color: '#FF5722', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...beliefWorkTechniques.map(tech => ({ ...tech, category: 'Работа с убеждениями', color: '#E91E63', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...integrativeNLPTechniques.map(tech => ({ ...tech, category: 'НЛП Интегративные', color: '#9C27B0', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...advancedCBTTechniques.map(tech => ({ ...tech, category: 'КПТ', color: '#2196F3', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...traumaInformedTechniques.map(tech => ({ ...tech, category: 'Работа с травмой', color: '#673AB7', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...integrativeTherapyTechniques.map(tech => ({ ...tech, category: 'Интегративные методы', color: '#4CAF50', difficulty: (tech as any).difficulty ?? 'intermediate' })),
...allExpandedTechniques.map(tech => ({ ...tech, category: 'НЛП Расширенные', color: '#00BCD4', difficulty: (tech as any).difficulty ?? 'intermediate' }))
];

interface Technique {
Expand Down Expand Up @@ -128,6 +128,7 @@ const MemoizedTechniqueCard = React.memo(({ technique, onPress }: {
</Animated.View>
);
});
MemoizedTechniqueCard.displayName = 'MemoizedTechniqueCard';

const MemoizedFilterChip = React.memo(({ label, selected, onPress, color }: {
label: string;
Expand Down Expand Up @@ -168,6 +169,7 @@ const MemoizedFilterChip = React.memo(({ label, selected, onPress, color }: {
</Animated.View>
);
});
MemoizedFilterChip.displayName = 'MemoizedFilterChip';

export default function EnhancedExercisesPage() {
const insets = useSafeAreaInsets();
Expand Down
4 changes: 3 additions & 1 deletion app/(tabs)/enhanced-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const MemoizedThemeCard = React.memo(({ theme, isSelected, onPress }: {
</Animated.View>
);
});
MemoizedThemeCard.displayName = 'MemoizedThemeCard';

const MemoizedSettingRow = React.memo(({ setting, onValueChange }: {
setting: Setting;
Expand Down Expand Up @@ -169,6 +170,7 @@ const MemoizedSettingRow = React.memo(({ setting, onValueChange }: {
</Animated.View>
);
});
MemoizedSettingRow.displayName = 'MemoizedSettingRow';

export default function EnhancedSettingsPage() {
const insets = useSafeAreaInsets();
Expand Down Expand Up @@ -481,7 +483,7 @@ export default function EnhancedSettingsPage() {
{settingSections.map((section) => (
<View key={section.id} style={styles.section}>
<View style={styles.sectionHeader}>
<MaterialIcons name={section.icon} size={24} color="#4CAF50" />
<MaterialIcons name={section.icon as keyof typeof MaterialIcons.glyphMap} size={24} color="#4CAF50" />
<View style={styles.sectionInfo}>
<Text style={styles.sectionTitle}>{section.title}</Text>
<Text style={styles.sectionDescription}>{section.description}</Text>
Expand Down
14 changes: 10 additions & 4 deletions app/(tabs)/exercises.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export default function ExercisesPage() {
submodalities: '#FF9800',
swish: '#F44336',
phobia: '#795548',
belief_change: '#607D8B'
belief_change: '#607D8B',
parts_integration: '#00BCD4',
meta_program: '#E91E63',
perceptual_positions: '#FF9800'
};

const nlpCategoryNames = {
Expand All @@ -74,8 +77,11 @@ export default function ExercisesPage() {
submodalities: 'Субмодальности',
swish: 'Свиш-паттерн',
phobia: 'Работа с фобиями',
belief_change: 'Изменение убеждений'
};
belief_change: 'Изменение убеждений',
parts_integration: 'Интеграция частей личности: согласование конфликтующих внутренних частей для достижения внутренней гармонии.',
meta_program: 'Мета-программы: выявление и изменение глубинных ментальных шаблонов, влияющих на восприятие и поведение.',
perceptual_positions: 'Позиции восприятия: взгляд на ситуацию с точки зрения себя, другого человека и наблюдателя для объективного понимания.'
} as Record<string, string>;

const startTechnique = (technique: NLPTechnique) => {
setSelectedTechnique(technique);
Expand Down Expand Up @@ -813,4 +819,4 @@ const styles = StyleSheet.create({
color: '#666',
lineHeight: 16
}
});
});
2 changes: 1 addition & 1 deletion app/(tabs)/gamification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export default function GamificationDashboard() {
setChallenges(challengesData);
};

const getRarityColor = (rarity: string) => {
const getRarityColor = (rarity: string): readonly [string, string] => {
switch (rarity) {
case 'common': return ['#95A5A6', '#BDC3C7'];
case 'rare': return ['#3498DB', '#5DADE2'];
Expand Down
8 changes: 4 additions & 4 deletions app/(tabs)/mini-games.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const BreathBubbleGame = ({ onGameComplete }: { onGameComplete: (score: number)
const [breathPhase, setBreathPhase] = useState<'inhale' | 'hold' | 'exhale'>('inhale');
const [phaseTime, setPhaseTime] = useState(0);
const [gameActive, setGameActive] = useState(false);
const animationRef = useRef<any>();
const animationRef = useRef<any>(null);

interface Bubble {
id: string;
Expand Down Expand Up @@ -220,7 +220,7 @@ const MindfulMazeGame = ({ onGameComplete }: { onGameComplete: (score: number) =
const [maze, setMaze] = useState<number[][]>([]);
const [collectibles, setCollectibles] = useState<{x: number, y: number}[]>([]);
const [gameTime, setGameTime] = useState(0);
const gameTimerRef = useRef<any>();
const gameTimerRef = useRef<any>(null);

const CELL_SIZE = 25;
const MAZE_SIZE = 15;
Expand Down Expand Up @@ -382,7 +382,7 @@ const DecisionChallengeGame = ({ onGameComplete }: { onGameComplete: (score: num
const [gameActive, setGameActive] = useState(false);
const [timeLeft, setTimeLeft] = useState(10);
const [scenarioIndex, setScenarioIndex] = useState(0);
const timerRef = useRef<any>();
const timerRef = useRef<any>(null);

const scenarios = [
{
Expand Down Expand Up @@ -742,7 +742,7 @@ const StressBallGame = ({ onGameComplete }: { onGameComplete: (score: number) =>
const [ballSize, setBallSize] = useState(new Animated.Value(80));
const [ballColor, setBallColor] = useState('#4CAF50');
const [breathingPhase, setBreathingPhase] = useState<'inhale' | 'exhale'>('inhale');
const gameTimerRef = useRef<any>();
const gameTimerRef = useRef<any>(null);

const startGame = () => {
setGameActive(true);
Expand Down
1 change: 1 addition & 0 deletions app/(tabs)/personalized-recommendations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ const MemoizedRecommendationCard = React.memo(({
</Animated.View>
);
});
MemoizedRecommendationCard.displayName = 'MemoizedRecommendationCard';

const PersonalizedRecommendationsPage = () => {
const insets = useSafeAreaInsets();
Expand Down
Loading
Loading