116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { chat } from '@chat-app/shared';
|
|
import type { ConversationSummary } from '@chat-app/shared/chat';
|
|
import { Stack, useRouter } from 'expo-router';
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
FlatList,
|
|
Pressable,
|
|
RefreshControl,
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
} from 'react-native';
|
|
|
|
import { ConversationRow } from '../../components/ConversationRow';
|
|
import { supabase } from '../../lib/supabase';
|
|
import { colors } from '../../theme/colors';
|
|
|
|
// Phase 1 conversation list. The last-message preview is the static
|
|
// fallback ('…') — fetching + decrypting last-messages per conversation
|
|
// is a Phase 1.5 follow-up. Tap → conversation detail.
|
|
export default function Chats() {
|
|
const router = useRouter();
|
|
const [list, setList] = useState<ConversationSummary[] | null>(null);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const load = useCallback(async () => {
|
|
setError(null);
|
|
try {
|
|
const result = await chat.listConversations(supabase);
|
|
setList(result.filter((c) => !c.archived));
|
|
} catch (err: unknown) {
|
|
setError(err instanceof Error ? err.message : 'Konversationen konnten nicht geladen werden');
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
}, [load]);
|
|
|
|
const onRefresh = useCallback(async () => {
|
|
setRefreshing(true);
|
|
await load();
|
|
setRefreshing(false);
|
|
}, [load]);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Stack.Screen
|
|
options={{
|
|
title: 'Chats',
|
|
headerStyle: { backgroundColor: colors.bg },
|
|
headerTitleStyle: { color: colors.text },
|
|
headerRight: () => (
|
|
<Pressable onPress={() => router.push('/(app)/settings')} hitSlop={10}>
|
|
<Text style={styles.logoutLink}>Einstellungen</Text>
|
|
</Pressable>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
{list === null && !error && (
|
|
<View style={styles.loading}>
|
|
<ActivityIndicator color={colors.accent} />
|
|
</View>
|
|
)}
|
|
|
|
{error && <Text style={styles.error}>{error}</Text>}
|
|
|
|
{list && list.length === 0 && !error && (
|
|
<View style={styles.empty}>
|
|
<Text style={styles.emptyText}>Noch keine Konversationen.</Text>
|
|
<Text style={styles.emptyHint}>
|
|
Lege dir auf dem Desktop einen Chat an — er taucht hier auf, sobald du
|
|
herunterziehst, um zu aktualisieren.
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
{list && list.length > 0 && (
|
|
<FlatList
|
|
data={list}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<ConversationRow
|
|
conversation={item}
|
|
lastMessagePreview="…"
|
|
onPress={() => router.push('/(app)/conversations/' + item.id)}
|
|
/>
|
|
)}
|
|
refreshControl={
|
|
<RefreshControl
|
|
refreshing={refreshing}
|
|
onRefresh={onRefresh}
|
|
tintColor={colors.accent}
|
|
/>
|
|
}
|
|
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: colors.bg },
|
|
loading: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
|
empty: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 32, gap: 8 },
|
|
emptyText: { color: colors.text, fontSize: 16, fontWeight: '600' },
|
|
emptyHint: { color: colors.textMuted, fontSize: 13, textAlign: 'center', lineHeight: 18 },
|
|
error: { color: colors.danger, padding: 16, textAlign: 'center' },
|
|
separator: { height: 1, backgroundColor: colors.border, marginLeft: 68 },
|
|
logoutLink: { color: colors.accent, fontSize: 14, fontWeight: '600', paddingHorizontal: 8 },
|
|
});
|