
* Add style prop to UserAvatar component for better customization * Refactor UserAvatar to use getAvatarSeed utility for consistent avatar generation * Fix React hook ordering issues in profile/overview.tsx to prevent crashes during auth state changes * Add proper state initialization and cleanup during authentication transitions * Ensure consistent fallback avatar display for unauthenticated users These changes improve stability during login/logout operations and provide better visual continuity with Robohash avatars when profile images aren't available.
8.0 KiB
Activity Tab
Last Updated: 2025-04-02
Status: Implemented
Related To: Profile Tab Overview, Progress Tab
Introduction
The Activity tab provides users with a summary of their workout activities, including statistics, recent workouts, and personal records. It serves as a quick overview dashboard for tracking workout progress and achievements.
Features
Feature | Status | Notes |
---|---|---|
Workout Stats Cards | ✅ Implemented | Shows completed workouts, templates, programs, and PRs |
Personal Records | ✅ Implemented | Lists recent personal records with dates |
Recent Workouts | ✅ Implemented | Shows most recent completed workouts |
Quick Action Buttons | ✅ Implemented | Navigation to start workout and view progress |
Authentication Detection | ✅ Implemented | Login prompt for unauthenticated users |
Integration with Analytics | ✅ Implemented | Uses AnalyticsService for workout statistics |
Implementation Details
The Activity tab is implemented in app/(tabs)/profile/activity.tsx
. It integrates with the AnalyticsService for personal records and the WorkoutService for workout history.
Stats Cards Implementation
The Activity tab displays four key statistics in card format:
{/* Stats Cards - Row 1 */}
<View className="flex-row px-4 pt-4">
<View className="flex-1 pr-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Dumbbell size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{completedWorkouts}</Text>
<Text className="text-muted-foreground">Workouts</Text>
</CardContent>
</Card>
</View>
<View className="flex-1 pl-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Calendar size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{totalTemplates}</Text>
<Text className="text-muted-foreground">Templates</Text>
</CardContent>
</Card>
</View>
</View>
{/* Stats Cards - Row 2 */}
<View className="flex-row px-4 pt-4 mb-4">
<View className="flex-1 pr-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<BarChart2 size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{totalPrograms}</Text>
<Text className="text-muted-foreground">Programs</Text>
</CardContent>
</Card>
</View>
<View className="flex-1 pl-2">
<Card>
<CardContent className="p-4 items-center justify-center">
<Award size={24} className="text-primary mb-2" />
<Text className="text-2xl font-bold">{records.length}</Text>
<Text className="text-muted-foreground">PRs</Text>
</CardContent>
</Card>
</View>
</View>
Personal Records Implementation
The Activity tab displays the user's personal records:
{/* Personal Records */}
<Card className="mx-4 mb-4">
<CardContent className="p-4">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-lg font-semibold">Personal Records</Text>
<Pressable onPress={() => router.push('/profile/progress')}>
<Text className="text-primary">View All</Text>
</Pressable>
</View>
{records.length === 0 ? (
<Text className="text-muted-foreground text-center py-4">
No personal records yet. Complete more workouts to see your progress.
</Text>
) : (
records.map((record) => (
<View key={record.id} className="py-2 border-b border-border">
<Text className="font-medium">{record.exerciseName}</Text>
<Text>{record.value} {record.unit} × {record.reps} reps</Text>
<Text className="text-muted-foreground text-sm">
{formatDistanceToNow(new Date(record.date), { addSuffix: true })}
</Text>
</View>
))
)}
</CardContent>
</Card>
Recent Workouts Implementation
The tab also shows the user's most recent completed workouts:
{/* Recent Workouts */}
<Card className="mx-4 mb-4">
<CardContent className="p-4">
<View className="flex-row justify-between items-center mb-4">
<Text className="text-lg font-semibold">Recent Workouts</Text>
<Pressable onPress={() => router.push('/history/workoutHistory')}>
<Text className="text-primary">View All</Text>
</Pressable>
</View>
{workouts && workouts.length > 0 ? (
workouts
.filter(workout => workout.isCompleted)
.slice(0, 2)
.map(workout => (
<View key={workout.id} className="mb-3">
<WorkoutCard
workout={workout}
showDate={true}
showExercises={false}
/>
</View>
))
) : (
<Text className="text-muted-foreground text-center py-4">
No recent workouts. Complete a workout to see it here.
</Text>
)}
</CardContent>
</Card>
Analytics Integration
The Activity tab integrates with the AnalyticsService to display personal records:
// Load personal records
useEffect(() => {
async function loadRecords() {
if (!isAuthenticated) return;
try {
setLoading(true);
const personalRecords = await analytics.getPersonalRecords(undefined, 3);
setRecords(personalRecords);
} catch (error) {
console.error('Error loading personal records:', error);
} finally {
setLoading(false);
}
}
loadRecords();
}, [isAuthenticated, analytics]);
Technical Considerations
Authentication Handling
Like other profile tabs, the Activity tab handles authentication with a consistent pattern:
// Show different UI when not authenticated
if (!isAuthenticated) {
return <NostrProfileLogin message="Login with your Nostr private key to view your activity and stats." />;
}
Data Loading
The component manages loading states and provides appropriate UI feedback:
if (loading || workoutsLoading || templatesLoading) {
return (
<View className="flex-1 items-center justify-center">
<ActivityIndicator />
</View>
);
}
Hook Dependencies
The component carefully manages effect hook dependencies to prevent unnecessary re-renders:
useEffect(() => {
async function loadRecords() {
// ...implementation
}
loadRecords();
}, [isAuthenticated, analytics]); // Only rerun when these dependencies change
User Experience Flow
-
Authentication Check:
- If user is not authenticated, display NostrProfileLogin component
- If authenticated, proceed to load activity data
-
Data Loading:
- Fetch completed workouts count from WorkoutService
- Load templates count from TemplateService
- Fetch personal records from AnalyticsService
- Show loading indicator during data fetch
-
Data Display:
- Show stats cards with summary numbers
- Display recent personal records with details
- Show recent completed workouts with WorkoutCard component
- Provide quick action buttons for common tasks
-
Navigation Options:
- "View All" links to relevant detailed screens (Progress, History)
- Quick action buttons for starting a workout or viewing progress
Future Enhancements
- Programs Integration: Complete implementation of programs count when feature is available
- Additional Stats: Add more workout statistics like total volume, duration
- Enhanced Personal Records: Add visual indicators for progress trends
- Streak Tracking: Add workout streak and consistency metrics
- Workout Insights: Add AI-generated insights based on workout patterns
Related Documentation
- Profile Overview - General overview of the Profile tab
- Progress Tab - Documentation for the detailed progress tracking tab
- Analytics Service - Details on workout analytics implementation
- WorkoutCard Component - Documentation for the WorkoutCard component used in this tab