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
73 changes: 38 additions & 35 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,20 @@ function FeedHeaderTitle() {
const { filter, setFilter } = useFeedFilter();
const [showDropdown, setShowDropdown] = useState(false);

const filters:
('Skatehive' | 'Recent' | 'Following' | 'Curated' | 'Trending')[] =
['Skatehive', 'Recent', 'Following', 'Curated', 'Trending'];
const filters: ('Recent' | 'Following' | 'Trending')[] = ['Recent', 'Following', 'Trending'];

const isSkatehiveOrRecent = filter === 'Skatehive' || filter === 'Recent';

return (
<View>
{/* TODO: Add filter dropdown back in */}
{/* <Pressable onPress={() => setShowDropdown(true)} style={{ flexDirection: 'row', alignItems: 'center' }}> */}
<Text style={{ fontSize: theme.fontSizes.lg, fontFamily: theme.fonts.bold, color: theme.colors.text }}>
{/* {filter} */}
Skatehive
</Text>
{/* <Ionicons name="chevron-down" size={18} color={theme.colors.text} style={{ marginLeft: 4 }} /> */}
{/* </Pressable> */}
<Pressable onPress={() => setShowDropdown(true)} style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ fontSize: theme.fontSizes.lg, fontFamily: theme.fonts.bold, color: theme.colors.text }}>
{isSkatehiveOrRecent ? 'Skatehive' : filter}
</Text>
{!isSkatehiveOrRecent && (
<Ionicons name="chevron-down" size={18} color={theme.colors.text} style={{ marginLeft: 4 }} />
)}
</Pressable>

<Modal
visible={showDropdown}
Expand All @@ -124,33 +124,36 @@ function FeedHeaderTitle() {
onPress={() => setShowDropdown(false)}
>
<View style={{ backgroundColor: theme.colors.secondaryCard, borderRadius: 12, padding: 8, width: 200, borderWidth: 1, borderColor: theme.colors.border }}>
{filters.map((f) => (
<Pressable
key={f}
onPress={() => {
setFilter(f);
setShowDropdown(false);
}}
style={{
paddingVertical: 12,
paddingHorizontal: 16,
backgroundColor: filter === f ? 'rgba(50, 205, 50, 0.1)' : 'transparent',
borderRadius: 8,
marginBottom: 4,
}}
>
<Text style={{
color: filter === f ? theme.colors.primary : theme.colors.text,
fontFamily: filter === f ? theme.fonts.bold : theme.fonts.regular
}}>
{f}
</Text>
</Pressable>
))}
{filters.map((f) => {
const isActive = filter === f || (f === 'Recent' && filter === 'Skatehive');
return (
<Pressable
key={f}
onPress={() => {
setFilter(f);
setShowDropdown(false);
}}
style={{
paddingVertical: 12,
paddingHorizontal: 16,
backgroundColor: isActive ? 'rgba(50, 205, 50, 0.1)' : 'transparent',
borderRadius: 8,
marginBottom: 4,
}}
>
<Text style={{
color: isActive ? theme.colors.primary : theme.colors.text,
fontFamily: isActive ? theme.fonts.bold : theme.fonts.regular
}}>
{f}
</Text>
</Pressable>
);
})}
</View>
</Pressable>
</Modal>
</View >
</View>
);
}

Expand Down
16 changes: 8 additions & 8 deletions app/(tabs)/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
TouchableWithoutFeedback,
View,
ScrollView,
ActivityIndicator,
Alert,
StyleSheet,
} from "react-native";
Expand Down Expand Up @@ -37,6 +36,7 @@ import {
getLastSnapsContainer,
} from "~/lib/hive-utils";
import { theme } from "~/lib/theme";
import { ThemedLoading } from "~/components/ui/ThemedLoading";

export default function CreatePost() {
const { username, session } = useAuth();
Expand Down Expand Up @@ -363,10 +363,7 @@ export default function CreatePost() {
{isSelectingMedia ? (
<>
<View style={styles.loadingContainer}>
<ActivityIndicator
size="small"
color={theme.colors.text}
/>
<ThemedLoading size="small" />
</View>
<Text style={styles.buttonTextSecondary}>Selecting...</Text>
</>
Expand All @@ -388,9 +385,12 @@ export default function CreatePost() {
onPress={handlePost}
disabled={(!content.trim() && !media) || isUploading}
>
<Text style={styles.shareButtonText}>
{isUploading ? "Publishing..." : "Share"}
</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
{isUploading && <ThemedLoading size="small" />}
<Text style={styles.shareButtonText}>
{isUploading ? "Publishing..." : "Share"}
</Text>
</View>
</Button>
</View>

Expand Down
78 changes: 24 additions & 54 deletions app/(tabs)/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
View,
ScrollView,
Image,
ActivityIndicator,
Pressable,
RefreshControl,
StyleSheet,
Expand Down Expand Up @@ -37,46 +36,13 @@ import type { Discussion } from '@hiveio/dhive';
import { extractMediaFromBody } from '~/lib/utils';
import { GridVideoTile } from "~/components/Profile/GridVideoTile";
import { VideoPlayer } from '~/components/Feed/VideoPlayer';
import { ThemedLoading } from "~/components/ui/ThemedLoading";
import { GridSkeleton } from "~/components/ui/Skeletons";

const GRID_COLS = 3;
const GRID_GAP = 2;
const SCREEN_WIDTH = Dimensions.get('window').width;

// Skeleton grid shown while posts load
const SkeletonTile = React.memo(({ size, delay }: { size: number; delay: number }) => {
const opacity = useRef(new Animated.Value(0.3)).current;

useEffect(() => {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(opacity, { toValue: 0.6, duration: 800, delay, useNativeDriver: true }),
Animated.timing(opacity, { toValue: 0.3, duration: 800, useNativeDriver: true }),
])
);
pulse.start();
return () => pulse.stop();
}, []);

return <Animated.View style={{ width: size, height: size, backgroundColor: theme.colors.secondaryCard, opacity }} />;
});

const GridSkeleton = ({ tileSize }: { tileSize: number }) => (
<View style={[skeletonStyles.container, { width: SCREEN_WIDTH }]}>
{Array.from({ length: 12 }).map((_, i) => (
<SkeletonTile key={i} size={tileSize} delay={(i % 3) * 150} />
))}
</View>
);

const skeletonStyles = StyleSheet.create({
container: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: GRID_GAP,
justifyContent: 'flex-start',
},
});

// Map common country names/codes to flag emojis
function countryToFlag(location: string): string {
const loc = location.trim().toUpperCase();
Expand Down Expand Up @@ -237,15 +203,15 @@ export default function ProfileScreen() {
const { hiveAccount, isLoading: isLoadingProfile, error } = useHiveAccount(profileUsername);

// --- Fetching Logic (API vs RPC) ---

// 1. RPC Fallback (original hook)
// We only pass the username if the API is disabled to save resources
const {
posts: rpcPosts,
isLoading: isRpcLoading,
loadNextPage: loadNextPageRpc,
hasMore: rpcHasMore,
refresh: refreshRpc
const {
posts: rpcPosts,
isLoading: isRpcLoading,
loadNextPage: loadNextPageRpc,
hasMore: rpcHasMore,
refresh: refreshRpc
} = useUserComments(SnapConfig.useApi ? null : profileUsername, blockedList);

// 2. API Logic (New migration)
Expand Down Expand Up @@ -273,14 +239,14 @@ export default function ProfileScreen() {
setIsApiLoading(true);
setApiError(null);
console.log(`[Profile Snaps] Fetching page ${page} for @${profileUsername} (refresh: ${refresh})`);

const url = `${API_BASE_URL}/feed?author=${profileUsername}&page=${page}&limit=${API_LIMIT}`;
const response = await fetch(url);

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const result = await response.json();

if (result.success) {
Expand Down Expand Up @@ -332,7 +298,7 @@ export default function ProfileScreen() {
// Initial load or username change (for API only)
useEffect(() => {
if (!SnapConfig.useApi) return;

console.log(`[Profile Snaps] Resetting and initial load for @${profileUsername}`);
setApiPosts([]);
setApiPage(1);
Expand Down Expand Up @@ -539,7 +505,11 @@ export default function ProfileScreen() {
};

if (isLoadingProfile) {
return <LoadingScreen />;
return (
<View style={styles.container}>
<ThemedLoading type="auto" size="large" />
</View>
);
}

// Only show error for non-SPECTATOR users when there's an actual error or missing account
Expand Down Expand Up @@ -615,7 +585,7 @@ export default function ProfileScreen() {
disabled={isBlockLoading}
>
{isBlockLoading ? (
<ActivityIndicator size="small" color={theme.colors.danger} />
<ThemedLoading size="small" color={theme.colors.danger} />
) : (
<Text style={[styles.followActionBtnText, { color: theme.colors.danger }]}>
Blocked
Expand All @@ -632,7 +602,7 @@ export default function ProfileScreen() {
disabled={isFollowLoading}
>
{isFollowLoading ? (
<ActivityIndicator size="small" color={isFollowing ? theme.colors.text : theme.colors.background} />
<ThemedLoading size="small" color={isFollowing ? theme.colors.text : theme.colors.background} />
) : (
<Text style={[
styles.followActionBtnText,
Expand Down Expand Up @@ -738,8 +708,8 @@ export default function ProfileScreen() {
return (
<View style={styles.errorFooter}>
<Text style={styles.errorFooterText}>{apiError}</Text>
<Pressable
style={styles.retryButton}
<Pressable
style={styles.retryButton}
onPress={() => fetchUserSnaps(apiPage + 1)}
>
<Ionicons name="refresh-outline" size={16} color={theme.colors.primary} />
Expand All @@ -749,10 +719,10 @@ export default function ProfileScreen() {
);
}

if (!isLoadingPosts) return null;
if (!isLoadingPosts || userPosts.length === 0) return null;
return (
<View style={styles.loadingFooter}>
<LoadingScreen />
<ThemedLoading size="small" />
</View>
);
};
Expand Down
6 changes: 3 additions & 3 deletions app/(tabs)/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import {
TextInput,
FlatList,
Pressable,
ActivityIndicator,
Dimensions,
ScrollView,
Keyboard
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { Text } from "~/components/ui/text";
import { theme } from "~/lib/theme";
import { ThemedLoading } from "~/components/ui/ThemedLoading";
import { SafeAreaView } from "react-native-safe-area-context";
import { useSearch, SearchType, TimeFilter } from "~/lib/hooks/useSearch";
import { PostCard } from "~/components/Feed/PostCard";
Expand Down Expand Up @@ -231,7 +231,7 @@ export default function SearchScreen() {
if (isSnapsFetchingNextPage) {
return (
<View style={styles.footerLoader}>
<ActivityIndicator color={theme.colors.primary} />
<ThemedLoading size="small" />
</View>
);
}
Expand Down Expand Up @@ -291,7 +291,7 @@ export default function SearchScreen() {
<View style={styles.resultsContainer}>
{isLoading && snaps.length === 0 && users.length === 0 ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={theme.colors.primary} />
<ThemedLoading size="large" />
</View>
) : (
searchType === 'users' ? (
Expand Down
Loading