pause
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { SymbolView } from 'expo-symbols';
|
||||
import { useLocalSearchParams } from 'expo-router';
|
||||
import RenderHtml from 'react-native-render-html';
|
||||
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
@@ -81,9 +82,46 @@ export default function ArticleDetailScreen() {
|
||||
});
|
||||
};
|
||||
|
||||
const extractImagesFromHtml = (html: string): string[] => {
|
||||
const imgRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
||||
const images: string[] = [];
|
||||
let match;
|
||||
while ((match = imgRegex.exec(html)) !== null) {
|
||||
if (match[1] && !images.includes(match[1])) {
|
||||
images.push(match[1]);
|
||||
}
|
||||
}
|
||||
return images;
|
||||
};
|
||||
|
||||
const getGalleryImages = (): string[] => {
|
||||
const images: string[] = [];
|
||||
if (article?.content) {
|
||||
images.push(...extractImagesFromHtml(article.content));
|
||||
}
|
||||
if (article?.description) {
|
||||
images.push(...extractImagesFromHtml(article.description));
|
||||
}
|
||||
return [...new Set(images)];
|
||||
};
|
||||
|
||||
const galleryImages = getGalleryImages();
|
||||
|
||||
const renderContent = (content?: string) => {
|
||||
if (!content) return null;
|
||||
|
||||
const isHtml = /<[a-z][\s\S]*>/i.test(content);
|
||||
|
||||
if (isHtml) {
|
||||
return (
|
||||
<RenderHtml
|
||||
contentWidth={350}
|
||||
source={{ html: content }}
|
||||
baseStyle={styles.content}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemedText style={styles.content}>
|
||||
{content}
|
||||
@@ -169,6 +207,26 @@ export default function ArticleDetailScreen() {
|
||||
</Pressable>
|
||||
</ThemedView>
|
||||
|
||||
{galleryImages.length > 0 && (
|
||||
<ThemedView style={styles.gallery}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.galleryContent}
|
||||
>
|
||||
{galleryImages.map((imgUrl, index) => (
|
||||
<Pressable key={index} onPress={() => Linking.openURL(imgUrl)}>
|
||||
<Image
|
||||
source={{ uri: imgUrl }}
|
||||
style={styles.galleryImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</Pressable>
|
||||
))}
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
)}
|
||||
|
||||
{article.description && (
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="default" style={styles.description}>
|
||||
@@ -244,6 +302,18 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
gallery: {
|
||||
paddingVertical: Spacing.two,
|
||||
},
|
||||
galleryContent: {
|
||||
paddingHorizontal: Spacing.four,
|
||||
gap: Spacing.two,
|
||||
},
|
||||
galleryImage: {
|
||||
width: 200,
|
||||
height: 150,
|
||||
borderRadius: Spacing.two,
|
||||
},
|
||||
section: {
|
||||
padding: Spacing.four,
|
||||
paddingTop: 0,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, TouchableOpacity, Linking } from 'react-native';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { FeedItem } from '@/types/feed';
|
||||
import { ThemedText } from './themed-text';
|
||||
import { ThemedView } from './themed-view';
|
||||
@@ -18,10 +19,13 @@ interface FeedItemCardProps {
|
||||
export function FeedItemCard({ item, onPress, onLongPress }: FeedItemCardProps) {
|
||||
const scheme = useColorScheme();
|
||||
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
||||
const router = useRouter();
|
||||
|
||||
const handlePress = () => {
|
||||
if (onPress) {
|
||||
onPress(item);
|
||||
} else if (item.id) {
|
||||
router.push(`/article/${item.id}` as any);
|
||||
} else if (item.link) {
|
||||
Linking.openURL(item.link);
|
||||
}
|
||||
@@ -63,7 +67,7 @@ export function FeedItemCard({ item, onPress, onLongPress }: FeedItemCardProps)
|
||||
return '';
|
||||
}, [item.content, item.description]);
|
||||
|
||||
return (
|
||||
const cardContent = (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
onPress={handlePress}
|
||||
@@ -110,6 +114,58 @@ export function FeedItemCard({ item, onPress, onLongPress }: FeedItemCardProps)
|
||||
</ThemedView>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
if (item.id) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, { backgroundColor: colors.background }]}
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<ThemedView style={styles.content}>
|
||||
{item.subscriptionTitle && (
|
||||
<ThemedText type="small" style={styles.feedTitle} numberOfLines={1}>
|
||||
{item.subscriptionTitle}
|
||||
</ThemedText>
|
||||
)}
|
||||
|
||||
<ThemedText type="smallBold" style={styles.title} numberOfLines={2}>
|
||||
{item.title}
|
||||
</ThemedText>
|
||||
|
||||
{excerpt && (
|
||||
<ThemedText type="small" style={styles.excerpt} numberOfLines={3}>
|
||||
{excerpt}
|
||||
</ThemedText>
|
||||
)}
|
||||
|
||||
<ThemedView style={styles.metaRow}>
|
||||
{item.author && (
|
||||
<ThemedText type="small" style={styles.author} numberOfLines={1}>
|
||||
{item.author}
|
||||
</ThemedText>
|
||||
)}
|
||||
{item.published && (
|
||||
<ThemedText type="small" style={styles.date}>
|
||||
{formatDate(item.published)}
|
||||
</ThemedText>
|
||||
)}
|
||||
</ThemedView>
|
||||
|
||||
{item.enclosure && item.enclosure.type.includes('audio') && (
|
||||
<ThemedView style={styles.audioBadge}>
|
||||
<ThemedText type="small" style={styles.audioBadgeText}>
|
||||
🎧 Audio
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
)}
|
||||
</ThemedView>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
return cardContent;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -387,7 +387,7 @@ export default function SettingsScreen() {
|
||||
<ThemedView style={styles.prefGroup}>
|
||||
<ThemedText style={styles.prefTitle}>Timezone</ThemedText>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
style={[styles.input, { backgroundColor: theme.backgroundElement }]}
|
||||
placeholder="e.g., UTC"
|
||||
value={accountSettings.timezone}
|
||||
onChangeText={(text) => handleAccountSettingChange('timezone', text)}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
interface BookmarkState {
|
||||
bookmarkedIds: Set<string>;
|
||||
@@ -50,7 +51,7 @@ export const useBookmarkStore = create<BookmarkState>()(
|
||||
}),
|
||||
{
|
||||
name: 'rssuper-bookmarks',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
partialize: (state) => ({
|
||||
bookmarkedIds: Array.from(state.bookmarkedIds),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user