Skip to content
Open
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
48 changes: 18 additions & 30 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
# Релиз v1.1.0 - Улучшение AI и Сообщества 🌿
# Релиз v1.5.0 - "Геймификация и глубокая связь" 🚀

В этом релизе мы значительно расширили возможности взаимодействия с AI-коучем и добавили полноценный раздел Сообщества.
## 🤖 AI-Коуч и Геймификация
- **Ежедневные испытания**: Внедрена система Daily Challenges. Получайте задания от коуча, выполняйте их и зарабатывайте очки опыта (XP).
- **Deep Linking**: Реализована возможность перехода из чата AI-Коуча напрямую к рекомендованным статьям в базе знаний.
- **Интеллектуальные подсказки**: Коуч теперь предлагает более контекстные действия на основе ваших ответов.

## 🚀 Новые функции
## 💬 Сообщество и Контент
- **Живое сообщество**: Внедрены анимации для взаимодействия с постами. Поддержка стала визуально приятнее!
- **Расширенная база знаний**: Добавлено 20+ новых мотивирующих цитат и советов на каждый день.
- **Истории успеха**: Добавлены новые реальные истории людей, победивших зависимость, для вашего вдохновения.

### 🤖 AI-Коуч 2.1
- **Быстрые ответы**: Теперь коуч предлагает варианты ответов, что делает диалог более быстрым и удобным.
- **Интерактивный анализ**: Вкладка "Анализ" теперь показывает выявленные триггеры и дает рекомендации по их преодолению.
- **Живой интерфейс**: Плавные анимации появления сообщений создают ощущение реального диалога.
## 🎨 Интерфейс и UX
- **Улучшенная навигация**: Раздел "База знаний" теперь поддерживает автоматическое открытие статей при переходе по ссылке.
- **Обновленный дизайн**: Карточки в сообществе стали более информативными и яркими за счет цветового кодирования категорий.
- **Lottie-анимации**: Добавлены новые эффекты для празднования ваших достижений.

### 🤝 Сообщество
- **Истории успеха**: Вдохновляйтесь опытом других людей, прошедших путь к трезвости.
- **Лента поддержки**: Общайтесь, задавайте вопросы и получайте поддержку от единомышленников.
- **Категории постов**: Мотивация, Вопросы, Достижения.

### 🎨 Дизайн и UX
- Единый визуальный стиль с градиентными заголовками.
- Улучшенные карточки контента с мягкими тенями и скруглениями.

## 🛠 Технические изменения
- Внедрен `CommunityService` для управления контентом сообщества.
- Оптимизирована производительность чата.
- Все тесты проходят успешно (5/5).
## 🛠 Технические улучшения
- Оптимизирована работа хука `useAICoachViewModel`.
- Исправлены ошибки навигации в Expo Router.
- Обновлена база данных статей и психологических знаний.

---
## 📦 Как собрать APK

Для сборки Android-приложения используйте Expo Application Services (EAS):

1. Установите EAS CLI: `npm install -g eas-cli`
2. Авторизуйтесь: `eas login`
3. Инициализируйте проект: `eas build:configure`
4. Запустите сборку: `eas build --platform android --profile preview`

*Сгенерированный APK будет доступен по ссылке в консоли после завершения сборки.*
*Мы продолжаем делать путь к трезвости осознанным и вдохновляющим. Спасибо, что вы с нами!*
17 changes: 17 additions & 0 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,20 @@
- [x] Создать `JournalService` для управления записями пользователя.
- [x] Подготовить релиз v1.4.0.
- [x] Обновить визуальные материалы (скриншоты).

# Список задач по улучшению приложения (Цикл 5) - ВЫПОЛНЕНО ✅

## 🤖 AI-Коуч и Геймификация
- [x] Внедрить систему "Ежедневных испытаний" (Daily Challenges) от AI.
- [x] Реализовать глубокие ссылки (Deep Linking) на статьи из чата коуча.
- [x] Улучшить алгоритм подбора рекомендаций на основе выполненных испытаний.

## 💬 Контент и Сообщество
- [x] Добавить 20+ новых мотивирующих цитат.
- [x] Расширить базу историй успеха и постов в сообществе.
- [x] Обновить UI раздела "Сообщество" (Lottie-анимации, улучшенные карточки).

## 🎨 Интерфейс и UX
- [x] Интегрировать выбор статьи по ID в разделе "База знаний".
- [x] Добавить визуальные индикаторы выполнения ежедневных испытаний.
- [x] Подготовить релиз v1.5.0 и обновить скриншоты.
87 changes: 86 additions & 1 deletion app/(tabs)/ai-coach.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,45 @@ import { MaterialIcons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import { useAICoachViewModel, ChatMessage } from '../../hooks/useAICoachViewModel';
import { AICoachChallenge } from '../../services/AICoachService';
import { useRouter } from 'expo-router';
import Animated, {
FadeInUp,
FadeInRight,
} from 'react-native-reanimated';

const { width: screenWidth } = Dimensions.get('window');

const ChallengeCard = React.memo(({ challenge, onComplete }: {
challenge: AICoachChallenge,
onComplete: (id: string) => void
}) => (
<Animated.View
entering={FadeInRight.delay(200)}
style={[styles.challengeCard, challenge.completed && styles.challengeCompleted]}
>
<View style={styles.challengeIconContainer}>
<MaterialIcons name={challenge.icon} size={24} color={challenge.completed ? "#FFF" : "#2E7D4A"} />
</View>
<View style={styles.challengeInfo}>
<Text style={[styles.challengeTitle, challenge.completed && styles.challengeCompletedText]}>
{challenge.title}
</Text>
<Text style={[styles.challengePoints, challenge.completed && styles.challengeCompletedText]}>
+{challenge.rewardPoints} XP
</Text>
</View>
{!challenge.completed && (
<TouchableOpacity
style={styles.completeButton}
onPress={() => onComplete(challenge.id)}
>
<MaterialIcons name="check" size={20} color="white" />
</TouchableOpacity>
)}
</Animated.View>
));

// Refactored Message component
const MessageBubble = React.memo(({ message, onArticlePress, onSpeak, isSpeaking }: {
message: ChatMessage,
Expand Down Expand Up @@ -110,6 +142,25 @@ export default function EnhancedAICoach() {
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
{vm.challenges && vm.challenges.length > 0 && (
<View style={styles.challengesSection}>
<Text style={styles.sectionSmallTitle}>Ежедневные испытания</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.challengesScroll}
>
{vm.challenges.map(ch => (
<ChallengeCard
key={ch.id}
challenge={ch}
onComplete={vm.completeChallenge}
/>
))}
</ScrollView>
</View>
)}

<ScrollView
ref={scrollViewRef}
style={styles.messagesContainer}
Expand All @@ -119,7 +170,7 @@ export default function EnhancedAICoach() {
<MessageBubble
key={m.id}
message={m}
onArticlePress={(id) => router.push('/articles')}
onArticlePress={(id) => router.push({ pathname: '/articles', params: { id } })}
onSpeak={vm.speak}
isSpeaking={vm.isSpeaking}
/>
Expand Down Expand Up @@ -316,6 +367,40 @@ const styles = StyleSheet.create({
statValue: { fontSize: 20, fontWeight: 'bold', color: '#2E7D4A' },
statLabel: { fontSize: 12, color: '#666' },
sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#333', marginBottom: 15 },
sectionSmallTitle: { fontSize: 14, fontWeight: 'bold', color: '#2E7D4A', marginHorizontal: 15, marginTop: 10, marginBottom: 5 },
challengesSection: { backgroundColor: '#E8F5E8', paddingVertical: 10 },
challengesScroll: { paddingHorizontal: 10, gap: 10 },
challengeCard: {
backgroundColor: 'white',
borderRadius: 12,
padding: 12,
flexDirection: 'row',
alignItems: 'center',
minWidth: 180,
elevation: 2,
gap: 10
},
challengeCompleted: { backgroundColor: '#2E7D4A' },
challengeIconContainer: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#F0F0F0',
alignItems: 'center',
justifyContent: 'center'
},
challengeInfo: { flex: 1 },
challengeTitle: { fontSize: 14, fontWeight: 'bold', color: '#333' },
challengePoints: { fontSize: 12, color: '#2E7D4A', fontWeight: '600' },
challengeCompletedText: { color: 'white' },
completeButton: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#2E7D4A',
alignItems: 'center',
justifyContent: 'center'
},
triggerCard: {
backgroundColor: 'white',
borderRadius: 16,
Expand Down
13 changes: 12 additions & 1 deletion app/(tabs)/articles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Образовательные статьи о борьбе с алкогольной зависимостью

import React, { useState, useMemo, useCallback } from 'react';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import {
View,
Text,
Expand All @@ -14,6 +14,7 @@ import {
import { MaterialIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useLocalSearchParams } from 'expo-router';
import Animated, {
useSharedValue,
useAnimatedStyle,
Expand Down Expand Up @@ -127,10 +128,20 @@ const MemoizedFilterChip = React.memo(({ label, selected, onPress, count }: {

export default function ArticlesPage() {
const insets = useSafeAreaInsets();
const { id } = useLocalSearchParams<{ id: string }>();
const [selectedCategory, setSelectedCategory] = useState<string>('Все');
const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedArticle, setSelectedArticle] = useState<Article | null>(null);

useEffect(() => {
if (id) {
const article = articles.find(a => a.id === id);
if (article) {
setSelectedArticle(article);
}
}
}, [id]);

const categories = useMemo(() => {
const cats = new Set(articles.map(a => a.category));
return ['Все', ...Array.from(cats)].sort();
Expand Down
105 changes: 92 additions & 13 deletions app/(tabs)/community.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { MaterialIcons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { LinearGradient } from 'expo-linear-gradient';
import { CommunityService, SuccessStory, SupportPost } from '../../services/communityService';
import LottieView from 'lottie-react-native';
import Animated, { FadeInUp, FadeInRight, useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';

const { width: screenWidth } = Dimensions.get('window');

Expand All @@ -24,6 +26,9 @@ const SuccessStoryCard = ({ story }: { story: SuccessStory }) => (
);

const SupportPostItem = ({ post }: { post: SupportPost }) => {
const [liked, setLiked] = React.useState(false);
const lottieRef = React.useRef<LottieView>(null);

const getCategoryIcon = (category: string) => {
switch (category) {
case 'motivation': return 'auto-awesome';
Expand All @@ -33,27 +38,74 @@ const SupportPostItem = ({ post }: { post: SupportPost }) => {
}
};

const getCategoryColor = (category: string) => {
switch (category) {
case 'motivation': return '#FFC107';
case 'question': return '#2196F3';
case 'milestone': return '#E91E63';
default: return '#2E7D4A';
}
};

const handleLike = () => {
if (!liked) {
lottieRef.current?.play();
setLiked(true);
} else {
setLiked(false);
}
};

return (
<View style={styles.postCard}>
<Animated.View entering={FadeInUp.delay(100)} style={styles.postCard}>
<View style={styles.postHeader}>
<View style={styles.categoryIconContainer}>
<MaterialIcons name={getCategoryIcon(post.category)} size={20} color="#2E7D4A" />
<View style={[styles.categoryIconContainer, { backgroundColor: getCategoryColor(post.category) + '20' }]}>
<MaterialIcons name={getCategoryIcon(post.category)} size={20} color={getCategoryColor(post.category)} />
</View>
<View style={styles.authorInfo}>
<Text style={styles.authorName}>{post.author}</Text>
<Text style={styles.timeAgo}>{post.timeAgo}</Text>
</View>
<View style={[styles.categoryBadge, { backgroundColor: getCategoryColor(post.category) }]}>
<Text style={styles.categoryBadgeText}>{post.category}</Text>
</View>
<Text style={styles.authorName}>{post.author}</Text>
<Text style={styles.timeAgo}>{post.timeAgo}</Text>
</View>

<Text style={styles.postContent}>{post.content}</Text>

<View style={styles.postFooter}>
<TouchableOpacity style={styles.actionButton}>
<MaterialIcons name="favorite-border" size={18} color="#666" />
<Text style={styles.actionText}>{post.likes}</Text>
<TouchableOpacity style={styles.actionButton} onPress={handleLike}>
<View style={styles.iconContainer}>
<MaterialIcons
name={liked ? "favorite" : "favorite-border"}
size={20}
color={liked ? "#E91E63" : "#666"}
/>
{liked && (
<LottieView
ref={lottieRef}
source={require('../../assets/lottie/celebration.json')}
style={styles.likeAnimation}
loop={false}
autoPlay={false}
/>
)}
</View>
<Text style={[styles.actionText, liked && { color: '#E91E63' }]}>
{liked ? post.likes + 1 : post.likes}
</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.actionButton}>
<MaterialIcons name="chat-bubble-outline" size={18} color="#666" />
<MaterialIcons name="chat-bubble-outline" size={20} color="#666" />
<Text style={styles.actionText}>{post.comments}</Text>
</TouchableOpacity>

<TouchableOpacity style={[styles.actionButton, { marginLeft: 'auto' }]}>
<MaterialIcons name="share" size={20} color="#666" />
</TouchableOpacity>
</View>
</View>
</Animated.View>
);
};

Expand Down Expand Up @@ -220,16 +272,29 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center'
},
authorInfo: {
flex: 1
},
authorName: {
fontSize: 15,
fontWeight: '600',
color: '#333',
flex: 1
color: '#333'
},
timeAgo: {
fontSize: 12,
fontSize: 11,
color: '#999'
},
categoryBadge: {
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 8,
},
categoryBadgeText: {
fontSize: 10,
color: 'white',
fontWeight: 'bold',
textTransform: 'uppercase'
},
postContent: {
fontSize: 15,
color: '#444',
Expand All @@ -252,6 +317,20 @@ const styles = StyleSheet.create({
fontSize: 14,
color: '#666'
},
iconContainer: {
position: 'relative',
width: 24,
height: 24,
alignItems: 'center',
justifyContent: 'center'
},
likeAnimation: {
position: 'absolute',
width: 80,
height: 80,
top: -28,
left: -28,
},
fab: {
position: 'absolute',
bottom: 25,
Expand Down
Loading
Loading