Files
sapiens-mobile/sapiense-ai-app/app/(tabs)/index.tsx
jungwoo choi 919afe56f2 feat: SAPIENS Mobile App - Initial commit
React Native mobile application for SAPIENS news platform.
Consolidated all previous history into single commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 14:30:25 +09:00

180 lines
5.2 KiB
TypeScript

import { useState } from 'react';
import { View, Text, FlatList, ActivityIndicator, StyleSheet, SafeAreaView, TouchableOpacity, Image } from 'react-native';
import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useOutlets } from '@/hooks/useApi';
import OutletCard from '@/components/OutletCard';
import BottomTabBar from '@/components/BottomTabBar';
import SearchModal from '@/components/SearchModal';
import LanguageModal from '@/components/LanguageModal';
import { useLanguage } from '@/contexts/LanguageContext';
export default function HomeScreen() {
const [activeFilter, setActiveFilter] = useState<'people' | 'topics' | 'companies'>('people');
const [showSearch, setShowSearch] = useState(false);
const [showLanguage, setShowLanguage] = useState(false);
const { language, setLanguage } = useLanguage();
const router = useRouter();
const { data: outlets, isLoading, error } = useOutlets(activeFilter, language);
if (error) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Error: {error.message}</Text>
<Text style={styles.text}>Check if the server is running on localhost:8050</Text>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<Image
source={require('@/assets/images/sapiens-logo.png')}
style={styles.logo}
resizeMode="contain"
/>
</View>
<View style={styles.headerRight}>
<TouchableOpacity style={styles.iconButton} onPress={() => setShowSearch(true)}>
<Ionicons name="search-outline" size={18} color="#333" />
</TouchableOpacity>
<TouchableOpacity style={styles.iconButton} onPress={() => setShowLanguage(true)}>
<Ionicons name="settings-outline" size={18} color="#333" />
</TouchableOpacity>
<TouchableOpacity style={styles.iconButton}>
<Ionicons name="person-circle-outline" size={20} color="#333" />
</TouchableOpacity>
</View>
</View>
{/* Content Area */}
<View style={styles.contentContainer}>
{showSearch ? (
<SearchModal
visible={showSearch}
onClose={() => setShowSearch(false)}
/>
) : (
<>
{/* Outlets List */}
{isLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.text}>Loading outlets...</Text>
</View>
) : (
<FlatList
data={outlets || []}
keyExtractor={(item: any, index) => `${activeFilter}-${item.id}-${index}`}
renderItem={({ item }: any) => (
<OutletCard
id={item.id}
name={item.name}
description={item.description || item.focusSubject || item.category}
category={item.category}
focusSubject={item.focusSubject}
avatar={item.avatar}
articleCount={item.articleCount || 0}
onPress={(id) => router.push(`/outlet/${id}`)}
/>
)}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<Text style={styles.emptyText}>No outlets found</Text>
}
/>
)}
</>
)}
</View>
{/* Bottom Tab Bar */}
<BottomTabBar
activeTab={activeFilter}
onTabChange={setActiveFilter}
/>
{/* Language Modal */}
<LanguageModal
visible={showLanguage}
onClose={() => setShowLanguage(false)}
currentLanguage={language}
onSelectLanguage={setLanguage}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
header: {
backgroundColor: '#fff',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
contentContainer: {
flex: 1,
overflow: 'hidden',
},
headerLeft: {
alignItems: 'flex-start',
},
logo: {
height: 16,
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
iconButton: {
padding: 8,
},
list: {
padding: 16,
paddingBottom: 16,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
text: {
fontSize: 14,
color: '#666',
marginTop: 10,
},
errorText: {
fontSize: 16,
color: 'red',
fontWeight: 'bold',
},
emptyText: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginTop: 20,
},
});