diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcb841..27b6e49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added "Sign with Amber" option to login screen - Added comprehensive documentation in docs/technical/nostr/external-signers.md - Added technical documentation in docs/technical/nostr/amber-integration-fixes.md +- Standardized login experience across profile screens + - Created reusable NostrProfileLogin component for consistent UI + - Added customizable messaging for context-specific instructions + - Standardized button styling and text formatting + - Improved visual hierarchy with consistent spacing ### Fixed +- Profile tab login experience + - Fixed error when accessing profile feed without authentication + - Created standardized login component across all profile screens + - Added conditional hook calling to prevent "rendered fewer hooks than expected" error + - Improved state management during authentication transitions + - Enhanced profile data loading with better error handling - Android: Fixed Amber external signer integration issues - Added extensive logging to better diagnose communication issues - Improved error handling in `AmberSignerModule.kt` diff --git a/app/(tabs)/profile/activity.tsx b/app/(tabs)/profile/activity.tsx index d99508a..d44fd4c 100644 --- a/app/(tabs)/profile/activity.tsx +++ b/app/(tabs)/profile/activity.tsx @@ -8,6 +8,7 @@ import { useNDKCurrentUser } from '@/lib/hooks/useNDK'; import { ActivityIndicator } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import NostrLoginSheet from '@/components/sheets/NostrLoginSheet'; +import NostrProfileLogin from '@/components/social/NostrProfileLogin'; import { useRouter } from 'expo-router'; import { useAnalytics } from '@/lib/hooks/useAnalytics'; import { PersonalRecord } from '@/lib/services/AnalyticsService'; @@ -54,25 +55,7 @@ export default function ActivityScreen() { // Show different UI when not authenticated if (!isAuthenticated) { - return ( - - - Login with your Nostr private key to view your activity and stats. - - - - {/* NostrLoginSheet */} - setIsLoginSheetOpen(false)} - /> - - ); + return ; } if (loading || workoutsLoading || templatesLoading) { diff --git a/app/(tabs)/profile/overview.tsx b/app/(tabs)/profile/overview.tsx index 63c0e68..c7bb338 100644 --- a/app/(tabs)/profile/overview.tsx +++ b/app/(tabs)/profile/overview.tsx @@ -1,5 +1,5 @@ // app/(tabs)/profile/overview.tsx -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { View, FlatList, RefreshControl, Pressable, TouchableOpacity, ImageBackground, Clipboard } from 'react-native'; import { Text } from '@/components/ui/text'; import { Button } from '@/components/ui/button'; @@ -7,6 +7,7 @@ import { useNDKCurrentUser } from '@/lib/hooks/useNDK'; import { ActivityIndicator } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import NostrLoginSheet from '@/components/sheets/NostrLoginSheet'; +import NostrProfileLogin from '@/components/social/NostrProfileLogin'; import EnhancedSocialPost from '@/components/social/EnhancedSocialPost'; import EmptyFeed from '@/components/social/EmptyFeed'; import { useSocialFeed } from '@/lib/hooks/useSocialFeed'; @@ -44,20 +45,31 @@ export default function OverviewScreen() { const theme = useTheme() as CustomTheme; const { currentUser, isAuthenticated } = useNDKCurrentUser(); const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false); - // Always use useSocialFeed regardless of authentication state to avoid hook inconsistency - // This prevents the "Rendered fewer hooks than expected" error when auth state changes - const { - feedItems, - loading, - refresh, - isOffline - } = useSocialFeed({ + // Initialize feed related state + const [feedItems, setFeedItems] = useState([]); + const [feedLoading, setFeedLoading] = useState(false); + const [isOffline, setIsOffline] = useState(false); + + // Only call useSocialFeed when authenticated to prevent the error + const socialFeed = isAuthenticated ? useSocialFeed({ feedType: 'profile', - // Always provide an array for authors, empty if not authenticated - // This way the hook is always called with the same pattern authors: currentUser?.pubkey ? [currentUser.pubkey] : [], limit: 30 - }); + }) : null; + + // Extract values from socialFeed when authenticated + const loading = isAuthenticated ? socialFeed?.loading || false : feedLoading; + const refresh = isAuthenticated + ? (socialFeed?.refresh ? socialFeed.refresh : () => Promise.resolve()) + : () => Promise.resolve(); + + // Update feedItems when socialFeed.feedItems changes + useEffect(() => { + if (isAuthenticated && socialFeed) { + setFeedItems(socialFeed.feedItems); + setIsOffline(socialFeed.isOffline); + } + }, [isAuthenticated, socialFeed?.feedItems, socialFeed?.isOffline]); // Convert to the format expected by the component const entries = React.useMemo(() => { @@ -359,26 +371,9 @@ export default function OverviewScreen() { // Render functions for different app states const renderLoginScreen = useCallback(() => { return ( - - - Login with your Nostr private key to view your profile and posts. - - - - {/* NostrLoginSheet */} - setIsLoginSheetOpen(false)} - /> - + ); - }, [isLoginSheetOpen]); + }, []); const renderLoadingScreen = useCallback(() => { return ( diff --git a/app/(tabs)/profile/progress.tsx b/app/(tabs)/profile/progress.tsx index a95f897..ec36c42 100644 --- a/app/(tabs)/profile/progress.tsx +++ b/app/(tabs)/profile/progress.tsx @@ -1,5 +1,6 @@ // app/(tabs)/profile/progress.tsx import React, { useState, useEffect } from 'react'; +import NostrProfileLogin from '@/components/social/NostrProfileLogin'; import { View, ScrollView, Switch, TouchableOpacity } from 'react-native'; import { Text } from '@/components/ui/text'; import { Button } from '@/components/ui/button'; @@ -167,13 +168,7 @@ export default function ProgressScreen() { }; if (!isAuthenticated) { - return ( - - - Log in to view your progress - - - ); + return ; } if (loading) { diff --git a/app/(tabs)/profile/settings.tsx b/app/(tabs)/profile/settings.tsx index 5ede01c..9027ead 100644 --- a/app/(tabs)/profile/settings.tsx +++ b/app/(tabs)/profile/settings.tsx @@ -8,6 +8,7 @@ import { useNDKCurrentUser, useNDKAuth } from '@/lib/hooks/useNDK'; import { ActivityIndicator } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import NostrLoginSheet from '@/components/sheets/NostrLoginSheet'; +import NostrProfileLogin from '@/components/social/NostrProfileLogin'; import TermsOfServiceModal from '@/components/TermsOfServiceModal'; import { useTheme } from '@react-navigation/native'; import type { CustomTheme } from '@/lib/theme'; @@ -25,26 +26,7 @@ export default function SettingsScreen() { // Show different UI when not authenticated if (!isAuthenticated) { - return ( - - - Login with your Nostr private key to access settings. - - - - {/* NostrLoginSheet */} - setIsLoginSheetOpen(false)} - /> - - ); + return ; } return ( diff --git a/components/social/NostrProfileLogin.tsx b/components/social/NostrProfileLogin.tsx new file mode 100644 index 0000000..0c84ccf --- /dev/null +++ b/components/social/NostrProfileLogin.tsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { Text } from '@/components/ui/text'; +import { Button } from '@/components/ui/button'; +import NostrLoginSheet from '@/components/sheets/NostrLoginSheet'; + +interface NostrProfileLoginProps { + message?: string; +} + +export default function NostrProfileLogin({ + message = "Login with your Nostr private key to access your profile data." +}: NostrProfileLoginProps) { + const [isLoginSheetOpen, setIsLoginSheetOpen] = useState(false); + + return ( + + + {message} + + + + {/* NostrLoginSheet */} + setIsLoginSheetOpen(false)} + /> + + ); +}