Practice and reinforce the concepts from Lesson 14
Build engaging social features by:
Time Limit: 10 minutes
Install Social Sharing Libraries:
npx expo install expo-sharing expo-media-library
npm install react-native-share
Basic Sharing Setup:
import * as Sharing from 'expo-sharing';
import { Share } from 'react-native';
const shareContent = async (content) => {
try {
const result = await Share.share({
message: content.text,
url: content.url,
title: content.title
});
if (result.action === Share.sharedAction) {
console.log('Content shared successfully');
}
} catch (error) {
console.error('Error sharing:', error);
}
};
const shareToStories = async (imageUri) => {
const isAvailable = await Sharing.isAvailableAsync();
if (isAvailable) {
await Sharing.shareAsync(imageUri, {
mimeType: 'image/jpeg',
dialogTitle: 'Share to your story'
});
}
};
✅ Checkpoint: Successfully share content from your app!
Time Limit: 5 minutes
Create a basic user profile system:
import React, { useState } from 'react';
import { View, Text, Image, TouchableOpacity } from 'react-native';
const UserProfile = ({ user, isOwnProfile, onFollow }) => {
const [isFollowing, setIsFollowing] = useState(user.isFollowing);
const handleFollow = () => {
setIsFollowing(!isFollowing);
onFollow(user.id, !isFollowing);
};
return (
<View style={styles.profileContainer}>
<Image source={{ uri: user.avatar }} style={styles.avatar} />
<Text style={styles.username}>{user.username}</Text>
<Text style={styles.bio}>{user.bio}</Text>
<View style={styles.stats}>
<Text>{user.followers} Followers</Text>
<Text>{user.following} Following</Text>
<Text>{user.posts} Posts</Text>
</View>
{!isOwnProfile && (
<TouchableOpacity
style={[styles.followButton, isFollowing && styles.followingButton]}
onPress={handleFollow}
>
<Text style={styles.followButtonText}>
{isFollowing ? 'Following' : 'Follow'}
</Text>
</TouchableOpacity>
)}
</View>
);
};
✅ Checkpoint: Profile displays with follow functionality!
Build a comprehensive social feed with interactions:
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, TouchableOpacity, Image, TextInput, Modal } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
// Sample data structure
const samplePosts = [
{
id: '1',
user: {
id: 'user1',
username: 'fitnessfan_sarah',
avatar: 'https://randomuser.me/api/portraits/women/1.jpg'
},
content: {
text: 'Just completed my 5K run! Feeling amazing! 💪 #running #fitness',
image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b',
type: 'workout'
},
timestamp: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago
likes: 24,
comments: 8,
shares: 3,
isLiked: false,
isBookmarked: false
},
// Add more sample posts...
];
class SocialFeedManager {
constructor() {
this.posts = samplePosts;
this.users = new Map();
this.comments = new Map();
}
// Get posts with user engagement data
getPosts(userId, page = 1, limit = 10) {
return this.posts
.map(post => ({
...post,
isLiked: this.isPostLikedByUser(post.id, userId),
isBookmarked: this.isPostBookmarkedByUser(post.id, userId)
}))
.slice((page - 1) * limit, page * limit);
}
// Toggle like functionality
toggleLike(postId, userId) {
const post = this.posts.find(p => p.id === postId);
if (!post) return;
const currentlyLiked = this.isPostLikedByUser(postId, userId);
if (currentlyLiked) {
post.likes -= 1;
this.removeLike(postId, userId);
} else {
post.likes += 1;
this.addLike(postId, userId);
}
return !currentlyLiked;
}
// Add comment to post
addComment(postId, userId, text) {
const comment = {
id: Date.now().toString(),
postId,
userId,
text,
timestamp: new Date().toISOString(),
likes: 0
};
if (!this.comments.has(postId)) {
this.comments.set(postId, []);
}
this.comments.get(postId).push(comment);
// Update post comment count
const post = this.posts.find(p => p.id === postId);
if (post) post.comments += 1;
return comment;
}
// Get comments for a post
getComments(postId) {
return this.comments.get(postId) || [];
}
isPostLikedByUser(postId, userId) {
// In real app, check database or local storage
return false; // Simplified for demo
}
isPostBookmarkedByUser(postId, userId) {
return false; // Simplified for demo
}
addLike(postId, userId) {
// Store like relationship
}
removeLike(postId, userId) {
// Remove like relationship
}
}
// Main Social Feed Component
const SocialFeed = () => {
const [feedManager] = useState(() => new SocialFeedManager());
const [posts, setPosts] = useState([]);
const [selectedPost, setSelectedPost] = useState(null);
const [commentModalVisible, setCommentModalVisible] = useState(false);
const [newComment, setNewComment] = useState('');
const [refreshing, setRefreshing] = useState(false);
const currentUserId = 'current_user'; // In real app, get from auth
useEffect(() => {
loadPosts();
}, []);
const loadPosts = async () => {
const fetchedPosts = feedManager.getPosts(currentUserId);
setPosts(fetchedPosts);
};
const handleLike = (postId) => {
const newLikedState = feedManager.toggleLike(postId, currentUserId);
// Update local state
setPosts(prevPosts =>
prevPosts.map(post =>
post.id === postId
? {
...post,
isLiked: newLikedState,
likes: post.likes + (newLikedState ? 1 : -1)
}
: post
)
);
};
const handleComment = (post) => {
setSelectedPost(post);
setCommentModalVisible(true);
};
const submitComment = () => {
if (!newComment.trim()) return;
feedManager.addComment(selectedPost.id, currentUserId, newComment);
// Update post comment count in local state
setPosts(prevPosts =>
prevPosts.map(post =>
post.id === selectedPost.id
? { ...post, comments: post.comments + 1 }
: post
)
);
setNewComment('');
setCommentModalVisible(false);
};
const handleShare = async (post) => {
const shareContent = {
title: `Check out ${post.user.username}'s workout!`,
text: post.content.text,
url: `https://yourapp.com/posts/${post.id}`
};
try {
await Share.share(shareContent);
// Update share count
setPosts(prevPosts =>
prevPosts.map(p =>
p.id === post.id
? { ...p, shares: p.shares + 1 }
: p
)
);
} catch (error) {
console.error('Error sharing post:', error);
}
};
const renderPost = ({ item: post }) => (
<View style={styles.postContainer}>
{/* Post Header */}
<View style={styles.postHeader}>
<Image source={{ uri: post.user.avatar }} style={styles.userAvatar} />
<View style={styles.userInfo}>
<Text style={styles.username}>{post.user.username}</Text>
<Text style={styles.timestamp}>
{getRelativeTime(post.timestamp)}
</Text>
</View>
<TouchableOpacity style={styles.moreButton}>
<Ionicons name="ellipsis-horizontal" size={20} color="#666" />
</TouchableOpacity>
</View>
{/* Post Content */}
<Text style={styles.postText}>{post.content.text}</Text>
{post.content.image && (
<Image source={{ uri: post.content.image }} style={styles.postImage} />
)}
{/* Engagement Stats */}
<View style={styles.engagementStats}>
<Text style={styles.statText}>
{post.likes} likes • {post.comments} comments • {post.shares} shares
</Text>
</View>
{/* Action Buttons */}
<View style={styles.actionButtons}>
<TouchableOpacity
style={styles.actionButton}
onPress={() => handleLike(post.id)}
>
<Ionicons
name={post.isLiked ? 'heart' : 'heart-outline'}
size={24}
color={post.isLiked ? '#e74c3c' : '#666'}
/>
<Text style={[styles.actionText, post.isLiked && styles.likedText]}>
Like
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => handleComment(post)}
>
<Ionicons name="chatbubble-outline" size={24} color="#666" />
<Text style={styles.actionText}>Comment</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => handleShare(post)}
>
<Ionicons name="share-outline" size={24} color="#666" />
<Text style={styles.actionText}>Share</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => toggleBookmark(post.id)}
>
<Ionicons
name={post.isBookmarked ? 'bookmark' : 'bookmark-outline'}
size={24}
color={post.isBookmarked ? '#3498db' : '#666'}
/>
</TouchableOpacity>
</View>
</View>
);
return (
<View style={styles.container}>
<FlatList
data={posts}
renderItem={renderPost}
keyExtractor={(item) => item.id}
refreshing={refreshing}
onRefresh={loadPosts}
showsVerticalScrollIndicator={false}
/>
{/* Comment Modal */}
<Modal
visible={commentModalVisible}
animationType="slide"
presentationStyle="pageSheet"
>
<View style={styles.commentModal}>
<View style={styles.modalHeader}>
<TouchableOpacity onPress={() => setCommentModalVisible(false)}>
<Text style={styles.cancelButton}>Cancel</Text>
</TouchableOpacity>
<Text style={styles.modalTitle}>Comments</Text>
<TouchableOpacity onPress={submitComment}>
<Text style={styles.postButton}>Post</Text>
</TouchableOpacity>
</View>
{selectedPost && (
<CommentsList
postId={selectedPost.id}
feedManager={feedManager}
/>
)}
<View style={styles.commentInput}>
<TextInput
style={styles.textInput}
placeholder="Add a comment..."
value={newComment}
onChangeText={setNewComment}
multiline
/>
</View>
</View>
</Modal>
</View>
);
};
// Helper function for relative time
const getRelativeTime = (timestamp) => {
const now = new Date();
const postTime = new Date(timestamp);
const diffInHours = (now - postTime) / (1000 * 60 * 60);
if (diffInHours < 1) return 'Just now';
if (diffInHours < 24) return `${Math.floor(diffInHours)}h ago`;
return `${Math.floor(diffInHours / 24)}d ago`;
};
Your Mission:
Add sophisticated social mechanics:
// Social Features Extension
class AdvancedSocialFeatures {
constructor() {
this.followers = new Map();
this.following = new Map();
this.blockedUsers = new Set();
this.mentionNotifications = new Map();
}
// Mention system
parseMentions(text) {
const mentionRegex = /@(\w+)/g;
const mentions = [];
let match;
while ((match = mentionRegex.exec(text)) !== null) {
mentions.push({
username: match[1],
startIndex: match.index,
endIndex: match.index + match[0].length
});
}
return mentions;
}
// Hashtag system
parseHashtags(text) {
const hashtagRegex = /#(\w+)/g;
const hashtags = [];
let match;
while ((match = hashtagRegex.exec(text)) !== null) {
hashtags.push({
tag: match[1],
startIndex: match.index,
endIndex: match.index + match[0].length
});
}
return hashtags;
}
// Follow system with mutual connections
async followUser(followerId, targetUserId) {
if (!this.following.has(followerId)) {
this.following.set(followerId, new Set());
}
if (!this.followers.has(targetUserId)) {
this.followers.set(targetUserId, new Set());
}
this.following.get(followerId).add(targetUserId);
this.followers.get(targetUserId).add(followerId);
// Check for mutual connection
const isNowMutual = this.following.get(targetUserId)?.has(followerId);
if (isNowMutual) {
// Send mutual connection notification
await this.sendMutualConnectionNotification(followerId, targetUserId);
}
return {
success: true,
isMutual: isNowMutual
};
}
// Story/Status system
async createStory(userId, content) {
const story = {
id: Date.now().toString(),
userId,
content,
timestamp: new Date().toISOString(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours
views: new Set(),
reactions: new Map()
};
// Notify followers
const followers = this.followers.get(userId) || new Set();
for (const followerId of followers) {
await this.sendStoryNotification(followerId, story);
}
return story;
}
// Group/Community features
async createGroup(creatorId, groupData) {
const group = {
id: Date.now().toString(),
name: groupData.name,
description: groupData.description,
privacy: groupData.privacy, // 'public', 'private', 'secret'
creatorId,
members: new Set([creatorId]),
admins: new Set([creatorId]),
posts: [],
rules: groupData.rules || [],
createdAt: new Date().toISOString()
};
return group;
}
// Live activity feed
async getActivityFeed(userId) {
const activities = [];
// Get activities from followed users
const following = this.following.get(userId) || new Set();
for (const followedUserId of following) {
// Get recent posts, stories, etc.
const userActivities = await this.getUserRecentActivities(followedUserId);
activities.push(...userActivities);
}
// Sort by timestamp
return activities.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
}
}
// Real-time messaging component
const DirectMessaging = () => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
const [selectedUser, setSelectedUser] = useState(null);
const sendMessage = async () => {
if (!newMessage.trim() || !selectedUser) return;
const message = {
id: Date.now().toString(),
senderId: 'current_user',
recipientId: selectedUser.id,
text: newMessage,
timestamp: new Date().toISOString(),
read: false
};
setMessages(prev => [...prev, message]);
setNewMessage('');
// Send to server/real-time system
await sendMessageToServer(message);
};
return (
<View style={styles.messagingContainer}>
<FlatList
data={messages}
renderItem={({ item }) => (
<View style={[
styles.messageContainer,
item.senderId === 'current_user' ? styles.sentMessage : styles.receivedMessage
]}>
<Text style={styles.messageText}>{item.text}</Text>
<Text style={styles.messageTime}>
{new Date(item.timestamp).toLocaleTimeString()}
</Text>
</View>
)}
/>
<View style={styles.messageInputContainer}>
<TextInput
style={styles.messageInput}
value={newMessage}
onChangeText={setNewMessage}
placeholder="Type a message..."
multiline
/>
<TouchableOpacity onPress={sendMessage} style={styles.sendButton}>
<Ionicons name="send" size={24} color="#007AFF" />
</TouchableOpacity>
</View>
</View>
);
};
Add engaging social mechanics:
// Social gamification system
class SocialGamification {
constructor() {
this.achievements = new Map();
this.leaderboards = new Map();
this.challenges = new Map();
this.streaks = new Map();
}
// Social achievements
checkSocialAchievements(userId, action) {
const achievements = [];
switch (action.type) {
case 'POST_CREATED':
if (this.getUserPostCount(userId) === 1) {
achievements.push(this.unlockAchievement(userId, 'first_post'));
} else if (this.getUserPostCount(userId) === 100) {
achievements.push(this.unlockAchievement(userId, 'century_club'));
}
break;
case 'LIKE_RECEIVED':
const totalLikes = this.getUserTotalLikes(userId);
if (totalLikes === 100) {
achievements.push(this.unlockAchievement(userId, 'liked_influencer'));
}
break;
case 'FOLLOWER_GAINED':
const followerCount = this.getFollowerCount(userId);
if (followerCount === 100) {
achievements.push(this.unlockAchievement(userId, 'community_builder'));
}
break;
}
return achievements;
}
// Social challenges
createChallenge(creatorId, challengeData) {
const challenge = {
id: Date.now().toString(),
title: challengeData.title,
description: challengeData.description,
type: challengeData.type, // 'workout', 'diet', 'habit'
duration: challengeData.duration,
participants: new Set([creatorId]),
leaderboard: [],
prizes: challengeData.prizes,
startDate: challengeData.startDate,
endDate: challengeData.endDate,
rules: challengeData.rules
};
this.challenges.set(challenge.id, challenge);
return challenge;
}
// Social proof system
generateSocialProof(userId) {
const proofs = [];
// Recent achievements
const recentAchievements = this.getRecentAchievements(userId, 7); // Last 7 days
if (recentAchievements.length > 0) {
proofs.push({
type: 'achievement',
text: `Earned ${recentAchievements.length} achievements this week!`,
icon: '🏆'
});
}
// Streak information
const currentStreak = this.getCurrentStreak(userId);
if (currentStreak > 7) {
proofs.push({
type: 'streak',
text: `${currentStreak} day workout streak!`,
icon: '🔥'
});
}
// Social connections
const mutualConnections = this.getMutualConnections(userId);
if (mutualConnections.length > 0) {
proofs.push({
type: 'mutual',
text: `Connected with ${mutualConnections.length} mutual friends`,
icon: '🤝'
});
}
return proofs;
}
// Leaderboard system
updateLeaderboard(category, userId, score) {
if (!this.leaderboards.has(category)) {
this.leaderboards.set(category, []);
}
const leaderboard = this.leaderboards.get(category);
const existingEntry = leaderboard.find(entry => entry.userId === userId);
if (existingEntry) {
existingEntry.score = score;
} else {
leaderboard.push({ userId, score, timestamp: new Date().toISOString() });
}
// Sort and keep top 100
leaderboard.sort((a, b) => b.score - a.score);
this.leaderboards.set(category, leaderboard.slice(0, 100));
}
getLeaderboard(category, limit = 10) {
const leaderboard = this.leaderboards.get(category) || [];
return leaderboard.slice(0, limit);
}
}
// Social sharing integration
const SocialSharing = () => {
const shareWorkoutResult = async (workoutData) => {
const achievements = workoutData.achievements || [];
const personalBests = workoutData.personalBests || [];
let shareText = `Just completed a ${workoutData.type} workout! 💪\n`;
if (achievements.length > 0) {
shareText += `🏆 Achievements unlocked: ${achievements.join(', ')}\n`;
}
if (personalBests.length > 0) {
shareText += `🎯 New personal bests: ${personalBests.join(', ')}\n`;
}
shareText += `#FitnessJourney #WorkoutComplete`;
// Create custom image for social sharing
const shareImage = await generateWorkoutShareImage(workoutData);
const shareContent = {
title: 'Workout Complete!',
message: shareText,
url: `https://yourapp.com/workouts/${workoutData.id}`
};
// Platform-specific sharing
const platforms = [
{ name: 'Instagram Stories', action: () => shareToInstagramStories(shareImage) },
{ name: 'Facebook', action: () => shareToFacebook(shareContent) },
{ name: 'Twitter', action: () => shareToTwitter(shareContent) },
{ name: 'Copy Link', action: () => copyToClipboard(shareContent.url) }
];
// Show platform selection modal
showSharingModal(platforms);
};
const generateWorkoutShareImage = async (workoutData) => {
// Use canvas or image manipulation library to create custom share image
// Include workout stats, achievements, and branding
return 'path/to/generated/image.png';
};
return null; // This would be integrated into workout completion flow
};
Completed Successfully If:
Time Investment: 60 minutes total
Difficulty Level: Intermediate to Advanced
Prerequisites: State management, real-time data, user authentication
Tools Needed: Social sharing libraries, real-time backend, image processing tools