By the end of this lesson, you will be able to:
ℹ️ Info Definition: Social features are the connective tissue that transforms individual app experiences into community-driven platforms. From simple sharing to complex social networks, these features create the viral loops that make apps grow exponentially.
Social features are the difference between good apps and viral hits:
Feature | Purpose | Growth Impact | Examples |
---|---|---|---|
Content Sharing | Viral distribution | 400% discovery boost | Instagram posts, TikTok videos |
Friend Networks | User retention | 60% retention increase | Snapchat, WhatsApp |
Social Login | Reduce friction | 50% signup improvement | Facebook, Google login |
Leaderboards | Competition | 200% engagement lift | Duolingo, Strava |
Comments & Likes | Community building | 300% session length | YouTube, Twitter |
💡 Growth Insight: Apps that integrate social features within their first 3 months grow 5x faster than those that add them later!
// services/SocialAuth.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Google from 'expo-auth-session/providers/google';
import * as Facebook from 'expo-auth-session/providers/facebook';
import * as AppleAuthentication from 'expo-apple-authentication';
import { Platform } from 'react-native';
export interface SocialUser {
id: string;
email: string;
name: string;
avatar?: string;
provider: 'google' | 'facebook' | 'apple' | 'email';
verified: boolean;
createdAt: Date;
}
class SocialAuthService {
private static instance: SocialAuthService;
private currentUser: SocialUser | null = null;
static getInstance(): SocialAuthService {
if (!SocialAuthService.instance) {
SocialAuthService.instance = new SocialAuthService();
}
return SocialAuthService.instance;
}
async initializeAuth() {
try {
const storedUser = await AsyncStorage.getItem('user_session');
if (storedUser) {
this.currentUser = JSON.parse(storedUser);
return this.currentUser;
}
} catch (error) {
console.error('Error initializing auth:', error);
}
return null;
}
async signInWithGoogle(): Promise<SocialUser | null> {
try {
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: 'YOUR_EXPO_CLIENT_ID',
iosClientId: 'YOUR_IOS_CLIENT_ID',
androidClientId: 'YOUR_ANDROID_CLIENT_ID',
});
const result = await promptAsync();
if (result.type === 'success') {
const userInfo = await this.fetchGoogleUserInfo(result.authentication?.accessToken);
const socialUser: SocialUser = {
id: userInfo.id,
email: userInfo.email,
name: userInfo.name,
avatar: userInfo.picture,
provider: 'google',
verified: userInfo.verified_email,
createdAt: new Date(),
};
await this.saveUserSession(socialUser);
this.currentUser = socialUser;
return socialUser;
}
} catch (error) {
console.error('Google sign-in error:', error);
}
return null;
}
async signInWithApple(): Promise<SocialUser | null> {
if (Platform.OS !== 'ios') return null;
try {
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
});
if (credential) {
const socialUser: SocialUser = {
id: credential.user,
email: credential.email || '',
name: `${credential.fullName?.givenName || ''} ${credential.fullName?.familyName || ''}`.trim() || 'Apple User',
provider: 'apple',
verified: true,
createdAt: new Date(),
};
await this.saveUserSession(socialUser);
this.currentUser = socialUser;
return socialUser;
}
} catch (error) {
console.error('Apple sign-in error:', error);
}
return null;
}
private async fetchGoogleUserInfo(accessToken: string): Promise<any> {
const response = await fetch(`https://www.googleapis.com/oauth2/v2/userinfo?access_token=${accessToken}`);
return response.json();
}
private async saveUserSession(user: SocialUser) {
try {
await AsyncStorage.setItem('user_session', JSON.stringify(user));
} catch (error) {
console.error('Error saving user session:', error);
}
}
getCurrentUser(): SocialUser | null {
return this.currentUser;
}
async signOut(): Promise<void> {
try {
await AsyncStorage.removeItem('user_session');
this.currentUser = null;
} catch (error) {
console.error('Error signing out:', error);
}
}
isAuthenticated(): boolean {
return this.currentUser !== null;
}
}
export default SocialAuthService;
// components/SocialProfile.tsx
import React, { useState, useEffect } from 'react';
import {
View,
Text,
Image,
TouchableOpacity,
StyleSheet,
ScrollView,
Alert,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';
interface UserProfile {
id: string;
username: string;
displayName: string;
bio: string;
avatar: string;
followers: number;
following: number;
posts: number;
isFollowing: boolean;
isPrivate: boolean;
verified: boolean;
}
interface SocialProfileProps {
userId: string;
isOwnProfile: boolean;
onFollow?: (userId: string) => void;
onUnfollow?: (userId: string) => void;
onMessage?: (userId: string) => void;
}
export const SocialProfile: React.FC<SocialProfileProps> = ({
userId,
isOwnProfile,
onFollow,
onUnfollow,
onMessage,
}) => {
const [profile, setProfile] = useState<UserProfile | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadProfile();
}, [userId]);
const loadProfile = async () => {
try {
// Simulate API call
const mockProfile: UserProfile = {
id: userId,
username: 'johndoe',
displayName: 'John Doe',
bio: 'Mobile app developer • AI enthusiast • Building the future 🚀',
avatar: 'https://picsum.photos/150/150',
followers: 1234,
following: 567,
posts: 89,
isFollowing: false,
isPrivate: false,
verified: true,
};
setProfile(mockProfile);
} catch (error) {
console.error('Error loading profile:', error);
} finally {
setLoading(false);
}
};
const handleImagePicker = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.8,
});
if (!result.canceled) {
// Upload image and update profile
console.log('Selected image:', result.assets[0].uri);
}
};
const handleFollowAction = () => {
if (!profile) return;
if (profile.isFollowing) {
onUnfollow?.(userId);
setProfile(prev => prev ? { ...prev, isFollowing: false, followers: prev.followers - 1 } : null);
} else {
onFollow?.(userId);
setProfile(prev => prev ? { ...prev, isFollowing: true, followers: prev.followers + 1 } : null);
}
};
if (loading || !profile) {
return (
<View style={styles.loadingContainer}>
<Text>Loading profile...</Text>
</View>
);
}
return (
<ScrollView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity
style={styles.avatarContainer}
onPress={isOwnProfile ? handleImagePicker : undefined}
>
<Image source={{ uri: profile.avatar }} style={styles.avatar} />
{isOwnProfile && (
<View style={styles.editIconContainer}>
<Ionicons name="camera" size={16} color="white" />
</View>
)}
</TouchableOpacity>
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{profile.posts}</Text>
<Text style={styles.statLabel}>Posts</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{profile.followers.toLocaleString()}</Text>
<Text style={styles.statLabel}>Followers</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{profile.following.toLocaleString()}</Text>
<Text style={styles.statLabel}>Following</Text>
</View>
</View>
</View>
{/* Profile Info */}
<View style={styles.profileInfo}>
<View style={styles.nameContainer}>
<Text style={styles.displayName}>{profile.displayName}</Text>
{profile.verified && (
<Ionicons name="checkmark-circle" size={20} color="#1DA1F2" />
)}
</View>
<Text style={styles.username}>@{profile.username}</Text>
<Text style={styles.bio}>{profile.bio}</Text>
</View>
{/* Action Buttons */}
{!isOwnProfile && (
<View style={styles.actionButtons}>
<TouchableOpacity
style={[
styles.actionButton,
profile.isFollowing ? styles.followingButton : styles.followButton,
]}
onPress={handleFollowAction}
>
<Text style={[
styles.actionButtonText,
profile.isFollowing ? styles.followingButtonText : styles.followButtonText,
]}>
{profile.isFollowing ? 'Following' : 'Follow'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, styles.messageButton]}
onPress={() => onMessage?.(userId)}
>
<Ionicons name="chatbubble-outline" size={16} color="#007AFF" />
<Text style={styles.messageButtonText}>Message</Text>
</TouchableOpacity>
</View>
)}
{/* Content Tabs */}
<View style={styles.tabsContainer}>
<TouchableOpacity style={[styles.tab, styles.activeTab]}>
<Ionicons name="grid-outline" size={24} color="#000" />
</TouchableOpacity>
<TouchableOpacity style={styles.tab}>
<Ionicons name="play-outline" size={24} color="#8E8E93" />
</TouchableOpacity>
<TouchableOpacity style={styles.tab}>
<Ionicons name="bookmark-outline" size={24} color="#8E8E93" />
</TouchableOpacity>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
header: {
flexDirection: 'row',
paddingHorizontal: 20,
paddingVertical: 20,
alignItems: 'center',
},
avatarContainer: {
position: 'relative',
},
avatar: {
width: 100,
height: 100,
borderRadius: 50,
},
editIconContainer: {
position: 'absolute',
bottom: 0,
right: 0,
backgroundColor: '#007AFF',
borderRadius: 12,
width: 24,
height: 24,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: 'white',
},
statsContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
marginLeft: 30,
},
statItem: {
alignItems: 'center',
},
statNumber: {
fontSize: 18,
fontWeight: 'bold',
color: '#000000',
},
statLabel: {
fontSize: 14,
color: '#8E8E93',
marginTop: 2,
},
profileInfo: {
paddingHorizontal: 20,
paddingBottom: 20,
},
nameContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 4,
},
displayName: {
fontSize: 16,
fontWeight: 'bold',
color: '#000000',
marginRight: 8,
},
username: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 12,
},
bio: {
fontSize: 14,
color: '#000000',
lineHeight: 20,
},
actionButtons: {
flexDirection: 'row',
paddingHorizontal: 20,
paddingBottom: 20,
gap: 12,
},
actionButton: {
flex: 1,
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
followButton: {
backgroundColor: '#007AFF',
},
followingButton: {
backgroundColor: '#F2F2F7',
borderWidth: 1,
borderColor: '#D1D1D6',
},
messageButton: {
backgroundColor: '#F2F2F7',
borderWidth: 1,
borderColor: '#D1D1D6',
flexDirection: 'row',
gap: 8,
},
actionButtonText: {
fontSize: 16,
fontWeight: '600',
},
followButtonText: {
color: '#FFFFFF',
},
followingButtonText: {
color: '#000000',
},
messageButtonText: {
color: '#007AFF',
fontSize: 16,
fontWeight: '600',
},
tabsContainer: {
flexDirection: 'row',
borderTopWidth: 0.5,
borderTopColor: '#D1D1D6',
},
tab: {
flex: 1,
paddingVertical: 16,
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: 'transparent',
},
activeTab: {
borderBottomColor: '#000000',
},
});
export default SocialProfile;
// services/SharingService.ts
import * as Sharing from 'expo-sharing';
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
import { Platform } from 'react-native';
export interface ShareableContent {
type: 'text' | 'image' | 'video' | 'link' | 'achievement';
title: string;
description?: string;
url?: string;
imageUri?: string;
videoUri?: string;
metadata?: Record<string, any>;
}
export interface ShareOptions {
platforms?: ('native' | 'instagram' | 'twitter' | 'facebook' | 'tiktok')[];
customMessage?: string;
includeAppAttribution?: boolean;
trackAnalytics?: boolean;
}
class SharingService {
private static instance: SharingService;
private analyticsEnabled = true;
static getInstance(): SharingService {
if (!SharingService.instance) {
SharingService.instance = new SharingService();
}
return SharingService.instance;
}
async shareContent(content: ShareableContent, options: ShareOptions = {}): Promise<boolean> {
try {
const shareData = await this.prepareShareData(content, options);
if (options.platforms?.includes('native') || !options.platforms) {
return await this.shareNative(shareData);
}
// Handle specific platform sharing
if (options.platforms?.includes('instagram')) {
await this.shareToInstagram(content);
}
if (options.platforms?.includes('twitter')) {
await this.shareToTwitter(content, options.customMessage);
}
// Track sharing analytics
if (options.trackAnalytics && this.analyticsEnabled) {
this.trackShareEvent(content.type, options.platforms || ['native']);
}
return true;
} catch (error) {
console.error('Sharing error:', error);
return false;
}
}
private async prepareShareData(content: ShareableContent, options: ShareOptions) {
let message = content.title;
if (content.description) {
message += `\n\n${content.description}`;
}
if (options.customMessage) {
message = options.customMessage;
}
if (options.includeAppAttribution) {
message += '\n\nShared via MyAwesomeApp';
}
return {
title: content.title,
message,
url: content.url,
imageUri: content.imageUri,
videoUri: content.videoUri,
};
}
private async shareNative(shareData: any): Promise<boolean> {
try {
if (shareData.imageUri || shareData.videoUri) {
const mediaUri = shareData.imageUri || shareData.videoUri;
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(mediaUri, {
mimeType: shareData.imageUri ? 'image/jpeg' : 'video/mp4',
dialogTitle: shareData.title,
});
return true;
}
} else {
// Share text content
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(shareData.url || shareData.message);
return true;
}
}
} catch (error) {
console.error('Native sharing error:', error);
}
return false;
}
private async shareToInstagram(content: ShareableContent) {
if (!content.imageUri) {
throw new Error('Instagram sharing requires an image');
}
try {
// Save image to camera roll first
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status === 'granted') {
const asset = await MediaLibrary.createAssetAsync(content.imageUri);
// Instagram Stories sharing
const instagramUrl = `instagram-stories://share?source_application=your_app_id`;
// Note: This requires proper Instagram app integration
console.log('Instagram sharing prepared:', instagramUrl);
}
} catch (error) {
console.error('Instagram sharing error:', error);
}
}
private async shareToTwitter(content: ShareableContent, customMessage?: string) {
const message = customMessage || content.title;
const encodedMessage = encodeURIComponent(message);
let twitterUrl = `https://twitter.com/intent/tweet?text=${encodedMessage}`;
if (content.url) {
twitterUrl += `&url=${encodeURIComponent(content.url)}`;
}
// Open Twitter URL (web or app)
console.log('Twitter sharing URL:', twitterUrl);
}
async generateShareImage(content: ShareableContent): Promise<string | null> {
try {
// Create a shareable image with app branding
const shareImageData = {
width: 1080,
height: 1080,
title: content.title,
description: content.description,
backgroundImage: content.imageUri,
appLogo: 'path/to/app/logo.png',
};
// This would typically use a service like Bannerbear or generate locally
const shareImageUrl = await this.createShareImage(shareImageData);
return shareImageUrl;
} catch (error) {
console.error('Share image generation error:', error);
return null;
}
}
private async createShareImage(data: any): Promise<string> {
// Mock implementation - in reality, you'd use a service or Canvas API
return 'https://example.com/generated-share-image.jpg';
}
private trackShareEvent(contentType: string, platforms: string[]) {
// Analytics tracking
console.log('Share event:', {
contentType,
platforms,
timestamp: new Date().toISOString(),
});
}
async saveToDevice(content: ShareableContent): Promise<boolean> {
try {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
throw new Error('Media library permission required');
}
if (content.imageUri) {
await MediaLibrary.createAssetAsync(content.imageUri);
return true;
}
if (content.videoUri) {
await MediaLibrary.createAssetAsync(content.videoUri);
return true;
}
return false;
} catch (error) {
console.error('Save to device error:', error);
return false;
}
}
}
export default SharingService;
// components/SocialFeed.tsx
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
FlatList,
Image,
TouchableOpacity,
StyleSheet,
Dimensions,
Alert,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
interpolate,
} from 'react-native-reanimated';
interface FeedPost {
id: string;
userId: string;
username: string;
userAvatar: string;
verified: boolean;
content: string;
imageUrl?: string;
videoUrl?: string;
likes: number;
comments: number;
shares: number;
timestamp: Date;
isLiked: boolean;
isBookmarked: boolean;
}
interface SocialFeedProps {
onLike?: (postId: string) => void;
onComment?: (postId: string) => void;
onShare?: (postId: string) => void;
onBookmark?: (postId: string) => void;
onUserPress?: (userId: string) => void;
}
export const SocialFeed: React.FC<SocialFeedProps> = ({
onLike,
onComment,
onShare,
onBookmark,
onUserPress,
}) => {
const [posts, setPosts] = useState<FeedPost[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadPosts();
}, []);
const loadPosts = async () => {
try {
// Mock data - replace with actual API call
const mockPosts: FeedPost[] = [
{
id: '1',
userId: 'user1',
username: 'johndoe',
userAvatar: 'https://picsum.photos/50/50',
verified: true,
content: 'Just shipped my first React Native app! The AI-powered features are incredible 🚀 #ReactNative #AI #MobileDev',
imageUrl: 'https://picsum.photos/400/300',
likes: 127,
comments: 23,
shares: 8,
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
isLiked: false,
isBookmarked: false,
},
{
id: '2',
userId: 'user2',
username: 'sarahtech',
userAvatar: 'https://picsum.photos/51/51',
verified: false,
content: 'Amazing progress on my language learning app! Users are loving the gamified approach. Here\'s a sneak peek at the new UI ✨',
imageUrl: 'https://picsum.photos/401/301',
likes: 89,
comments: 15,
shares: 12,
timestamp: new Date(Date.now() - 5 * 60 * 60 * 1000), // 5 hours ago
isLiked: true,
isBookmarked: true,
},
];
setPosts(mockPosts);
} catch (error) {
console.error('Error loading posts:', error);
} finally {
setLoading(false);
setRefreshing(false);
}
};
const handleRefresh = () => {
setRefreshing(true);
loadPosts();
};
const formatTimestamp = (timestamp: Date) => {
const now = new Date();
const diffInHours = Math.floor((now.getTime() - timestamp.getTime()) / (1000 * 60 * 60));
if (diffInHours < 1) return 'Just now';
if (diffInHours < 24) return `${diffInHours}h ago`;
const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays < 7) return `${diffInDays}d ago`;
return timestamp.toLocaleDateString();
};
const renderPost = ({ item }: { item: FeedPost }) => {
return <PostItem post={item} />;
};
const PostItem = ({ post }: { post: FeedPost }) => {
const likeScale = useSharedValue(1);
const bookmarkScale = useSharedValue(1);
const animatedLikeStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: likeScale.value }],
};
});
const animatedBookmarkStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: bookmarkScale.value }],
};
});
const handleLike = () => {
likeScale.value = withSpring(1.2, {}, () => {
likeScale.value = withSpring(1);
});
const updatedPosts = posts.map(p =>
p.id === post.id
? {
...p,
isLiked: !p.isLiked,
likes: p.isLiked ? p.likes - 1 : p.likes + 1
}
: p
);
setPosts(updatedPosts);
onLike?.(post.id);
};
const handleBookmark = () => {
bookmarkScale.value = withSpring(1.2, {}, () => {
bookmarkScale.value = withSpring(1);
});
const updatedPosts = posts.map(p =>
p.id === post.id
? { ...p, isBookmarked: !p.isBookmarked }
: p
);
setPosts(updatedPosts);
onBookmark?.(post.id);
};
return (
<View style={styles.postContainer}>
{/* Header */}
<TouchableOpacity
style={styles.postHeader}
onPress={() => onUserPress?.(post.userId)}
>
<Image source={{ uri: post.userAvatar }} style={styles.userAvatar} />
<View style={styles.userInfo}>
<View style={styles.usernameContainer}>
<Text style={styles.username}>{post.username}</Text>
{post.verified && (
<Ionicons name="checkmark-circle" size={16} color="#1DA1F2" />
)}
</View>
<Text style={styles.timestamp}>{formatTimestamp(post.timestamp)}</Text>
</View>
<TouchableOpacity style={styles.moreButton}>
<Ionicons name="ellipsis-horizontal" size={20} color="#8E8E93" />
</TouchableOpacity>
</TouchableOpacity>
{/* Content */}
<Text style={styles.postContent}>{post.content}</Text>
{/* Media */}
{post.imageUrl && (
<Image source={{ uri: post.imageUrl }} style={styles.postImage} />
)}
{/* Actions */}
<View style={styles.actionsContainer}>
<View style={styles.leftActions}>
<Animated.View style={animatedLikeStyle}>
<TouchableOpacity style={styles.actionButton} onPress={handleLike}>
<Ionicons
name={post.isLiked ? "heart" : "heart-outline"}
size={24}
color={post.isLiked ? "#FF3B30" : "#8E8E93"}
/>
<Text style={[styles.actionText, post.isLiked && styles.likedText]}>
{post.likes}
</Text>
</TouchableOpacity>
</Animated.View>
<TouchableOpacity
style={styles.actionButton}
onPress={() => onComment?.(post.id)}
>
<Ionicons name="chatbubble-outline" size={24} color="#8E8E93" />
<Text style={styles.actionText}>{post.comments}</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.actionButton}
onPress={() => onShare?.(post.id)}
>
<Ionicons name="arrow-redo-outline" size={24} color="#8E8E93" />
<Text style={styles.actionText}>{post.shares}</Text>
</TouchableOpacity>
</View>
<Animated.View style={animatedBookmarkStyle}>
<TouchableOpacity onPress={handleBookmark}>
<Ionicons
name={post.isBookmarked ? "bookmark" : "bookmark-outline"}
size={24}
color={post.isBookmarked ? "#007AFF" : "#8E8E93"}
/>
</TouchableOpacity>
</Animated.View>
</View>
</View>
);
};
if (loading) {
return (
<View style={styles.loadingContainer}>
<Text>Loading feed...</Text>
</View>
);
}
return (
<FlatList
data={posts}
renderItem={renderPost}
keyExtractor={(item) => item.id}
style={styles.container}
showsVerticalScrollIndicator={false}
refreshing={refreshing}
onRefresh={handleRefresh}
ItemSeparatorComponent={() => <View style={styles.separator} />}
/>
);
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
postContainer: {
paddingVertical: 16,
paddingHorizontal: 16,
backgroundColor: '#FFFFFF',
},
postHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
userAvatar: {
width: 40,
height: 40,
borderRadius: 20,
marginRight: 12,
},
userInfo: {
flex: 1,
},
usernameContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
username: {
fontSize: 16,
fontWeight: '600',
color: '#000000',
},
timestamp: {
fontSize: 14,
color: '#8E8E93',
marginTop: 2,
},
moreButton: {
padding: 4,
},
postContent: {
fontSize: 15,
lineHeight: 20,
color: '#000000',
marginBottom: 12,
},
postImage: {
width: width - 32,
height: (width - 32) * 0.75,
borderRadius: 12,
marginBottom: 12,
},
actionsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
leftActions: {
flexDirection: 'row',
gap: 20,
},
actionButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
actionText: {
fontSize: 14,
color: '#8E8E93',
fontWeight: '500',
},
likedText: {
color: '#FF3B30',
},
separator: {
height: 0.5,
backgroundColor: '#C6C6C8',
marginLeft: 16,
},
});
export default SocialFeed;
// services/SocialIntelligence.ts
import OpenAI from 'openai';
interface UserInteraction {
userId: string;
action: 'like' | 'comment' | 'share' | 'follow' | 'view';
targetUserId?: string;
contentId?: string;
timestamp: Date;
metadata?: Record<string, any>;
}
interface SocialRecommendation {
type: 'user' | 'content' | 'group' | 'topic';
targetId: string;
score: number;
reason: string;
metadata?: Record<string, any>;
}
class SocialIntelligence {
private openai: OpenAI;
private interactions: UserInteraction[] = [];
constructor(apiKey: string) {
this.openai = new OpenAI({ apiKey });
}
trackInteraction(interaction: UserInteraction) {
this.interactions.push(interaction);
// Keep only last 1000 interactions for performance
if (this.interactions.length > 1000) {
this.interactions = this.interactions.slice(-1000);
}
}
async generateUserRecommendations(userId: string): Promise<SocialRecommendation[]> {
const userInteractions = this.interactions.filter(i => i.userId === userId);
if (userInteractions.length === 0) {
return this.getDefaultRecommendations();
}
const interactionSummary = this.analyzeUserBehavior(userInteractions);
const prompt = `
Analyze user social behavior and generate recommendations:
User Behavior Summary:
- Most liked content types: ${interactionSummary.preferredContentTypes.join(', ')}
- Active times: ${interactionSummary.activeTimes.join(', ')}
- Interaction patterns: ${interactionSummary.patterns.join(', ')}
- Social activity level: ${interactionSummary.activityLevel}
Generate 5 personalized recommendations for:
1. Users to follow
2. Content to engage with
3. Features to try
Return as JSON array with type, targetId, score (0-1), and reason.
`;
try {
const response = await this.openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
});
const recommendations = JSON.parse(response.choices[0].message.content || '[]');
return recommendations.map((rec: any) => ({
type: rec.type,
targetId: rec.targetId,
score: rec.score,
reason: rec.reason,
metadata: rec.metadata,
}));
} catch (error) {
console.error('AI recommendation error:', error);
return this.getFallbackRecommendations(userInteractions);
}
}
private analyzeUserBehavior(interactions: UserInteraction[]) {
// Analyze content type preferences
const contentTypes: Record<string, number> = {};
const activeTimes: Record<string, number> = {};
const patterns: string[] = [];
interactions.forEach(interaction => {
// Content type analysis
if (interaction.metadata?.contentType) {
contentTypes[interaction.metadata.contentType] =
(contentTypes[interaction.metadata.contentType] || 0) + 1;
}
// Active time analysis
const hour = interaction.timestamp.getHours();
const timeSlot = this.getTimeSlot(hour);
activeTimes[timeSlot] = (activeTimes[timeSlot] || 0) + 1;
});
// Pattern detection
const likeToCommentRatio =
interactions.filter(i => i.action === 'like').length /
Math.max(interactions.filter(i => i.action === 'comment').length, 1);
if (likeToCommentRatio > 10) patterns.push('passive_consumer');
else if (likeToCommentRatio < 3) patterns.push('active_commenter');
const shareCount = interactions.filter(i => i.action === 'share').length;
if (shareCount > interactions.length * 0.1) patterns.push('active_sharer');
return {
preferredContentTypes: Object.entries(contentTypes)
.sort(([,a], [,b]) => b - a)
.slice(0, 3)
.map(([type]) => type),
activeTimes: Object.entries(activeTimes)
.sort(([,a], [,b]) => b - a)
.slice(0, 2)
.map(([time]) => time),
patterns,
activityLevel: interactions.length > 50 ? 'high' : interactions.length > 20 ? 'medium' : 'low',
};
}
private getTimeSlot(hour: number): string {
if (hour >= 6 && hour < 12) return 'morning';
if (hour >= 12 && hour < 18) return 'afternoon';
if (hour >= 18 && hour < 22) return 'evening';
return 'night';
}
private getDefaultRecommendations(): SocialRecommendation[] {
return [
{
type: 'user',
targetId: 'featured_user_1',
score: 0.8,
reason: 'Popular creator in your area of interest',
},
{
type: 'content',
targetId: 'trending_content_1',
score: 0.7,
reason: 'Trending content similar to your interests',
},
{
type: 'group',
targetId: 'beginner_group_1',
score: 0.6,
reason: 'Perfect for getting started',
},
];
}
private getFallbackRecommendations(interactions: UserInteraction[]): SocialRecommendation[] {
// Simple rule-based recommendations based on basic patterns
const recommendations: SocialRecommendation[] = [];
const hasLikedTech = interactions.some(i =>
i.metadata?.contentType === 'tech' && i.action === 'like'
);
if (hasLikedTech) {
recommendations.push({
type: 'user',
targetId: 'tech_influencer_1',
score: 0.75,
reason: 'Based on your interest in technology content',
});
}
return recommendations;
}
async optimizeFeedAlgorithm(userId: string): Promise<{
contentWeights: Record<string, number>;
userAffinities: Record<string, number>;
engagementPredictions: Record<string, number>;
}> {
const userInteractions = this.interactions.filter(i => i.userId === userId);
// Calculate content type preferences
const contentWeights: Record<string, number> = {};
const userAffinities: Record<string, number> = {};
userInteractions.forEach(interaction => {
if (interaction.metadata?.contentType) {
const type = interaction.metadata.contentType;
const weight = this.getActionWeight(interaction.action);
contentWeights[type] = (contentWeights[type] || 0) + weight;
}
if (interaction.targetUserId) {
const weight = this.getActionWeight(interaction.action);
userAffinities[interaction.targetUserId] =
(userAffinities[interaction.targetUserId] || 0) + weight;
}
});
// Normalize weights
const maxContentWeight = Math.max(...Object.values(contentWeights));
const maxUserAffinity = Math.max(...Object.values(userAffinities));
Object.keys(contentWeights).forEach(key => {
contentWeights[key] = contentWeights[key] / maxContentWeight;
});
Object.keys(userAffinities).forEach(key => {
userAffinities[key] = userAffinities[key] / maxUserAffinity;
});
return {
contentWeights,
userAffinities,
engagementPredictions: {}, // Would implement ML-based predictions here
};
}
private getActionWeight(action: UserInteraction['action']): number {
switch (action) {
case 'view': return 1;
case 'like': return 3;
case 'comment': return 5;
case 'share': return 7;
case 'follow': return 10;
default: return 1;
}
}
}
export default SocialIntelligence;
In this lesson, you learned:
Code with AI: Try building these advanced social features.
Prompts to try:
Social features are the heart of viral apps - make them engaging, trustworthy, and designed for growth!