feat: add expo mobile application source code
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, Platform } from 'react-native';
|
||||
import { Feather } from '@expo/vector-icons';
|
||||
import { useAppTheme } from '../../context/ThemeContext';
|
||||
import { AppScreen } from '../../components/AppScreen';
|
||||
import { AISectionHeader, AISkeleton, AIPressable } from '../../components/UI';
|
||||
import { useTranslation } from '../../context/LanguageContext';
|
||||
import { MOCK_NOTIFICATIONS } from '../../constants/mocks';
|
||||
import { PALETTE } from '../../constants/theme';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
|
||||
const LIME = PALETTE.lime;
|
||||
|
||||
const TYPE_MAP: Record<string, { icon: any; color: string }> = {
|
||||
success: { icon: 'check-circle', color: LIME },
|
||||
info: { icon: 'info', color: '#3B82F6' },
|
||||
warning: { icon: 'alert-circle', color: '#F59E0B' },
|
||||
alert: { icon: 'shield', color: '#EF4444' },
|
||||
update: { icon: 'refresh-cw', color: '#8B5CF6' },
|
||||
};
|
||||
|
||||
// Mock data moved to constants/mocks.ts
|
||||
|
||||
export default function NotificationsScreen() {
|
||||
const { colors, isDark } = useAppTheme();
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setLoading(false), 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const cardBg = colors.surface;
|
||||
const border = colors.border;
|
||||
const subText = colors.textSecondary;
|
||||
|
||||
const renderSkeleton = () => (
|
||||
<View style={{ paddingHorizontal: 24, paddingTop: 56 }}>
|
||||
<AISkeleton width={140} height={32} style={{ marginBottom: 10 }} />
|
||||
<AISkeleton width={180} height={14} style={{ marginBottom: 32 }} />
|
||||
{[1, 2, 3, 4].map(i => (
|
||||
<View key={i} style={{ flexDirection: 'row', marginBottom: 12, gap: 14 }}>
|
||||
<AISkeleton width={48} height={48} radius={14} />
|
||||
<View style={{ flex: 1, justifyContent: 'center' }}>
|
||||
<AISkeleton width="70%" height={14} style={{ marginBottom: 8 }} />
|
||||
<AISkeleton width="40%" height={10} />
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (loading) return <AppScreen scrollable={false}>{renderSkeleton()}</AppScreen>;
|
||||
|
||||
return (
|
||||
<AppScreen>
|
||||
<View>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>{t.notifications || 'Activity'}</Text>
|
||||
<Text style={[styles.subtitle, { color: subText }]}>
|
||||
{MOCK_NOTIFICATIONS.length} {t.recentNotifications || 'recent notifications'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* List */}
|
||||
<View style={styles.list}>
|
||||
{MOCK_NOTIFICATIONS.map((item, index) => {
|
||||
const meta = TYPE_MAP[item.type] || TYPE_MAP.info;
|
||||
return (
|
||||
<AIPressable
|
||||
key={item.id}
|
||||
onPress={() => {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}}
|
||||
style={styles.notifPressable}
|
||||
>
|
||||
<View style={[styles.card, { backgroundColor: cardBg, borderColor: border }]}>
|
||||
{/* Left: colored icon */}
|
||||
<View style={[styles.iconBox, { backgroundColor: `${meta.color}18` }]}>
|
||||
<Feather name={meta.icon} size={22} color={meta.color} />
|
||||
</View>
|
||||
|
||||
{/* Body */}
|
||||
<View style={styles.body}>
|
||||
<View style={styles.topRow}>
|
||||
<Text style={[styles.notifTitle, { color: colors.text }]} numberOfLines={1}>
|
||||
{item.title}
|
||||
</Text>
|
||||
<Text style={[styles.time, { color: subText }]}>{item.time}</Text>
|
||||
</View>
|
||||
<Text style={[styles.desc, { color: subText }]} numberOfLines={2}>
|
||||
{item.desc}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</AIPressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<View style={{ height: 110 }} />
|
||||
</View>
|
||||
</AppScreen>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: { paddingHorizontal: 24, paddingTop: 20, marginBottom: 22 },
|
||||
title: { fontSize: 32, fontFamily: 'Outfit_800ExtraBold', letterSpacing: -0.5 },
|
||||
subtitle: { fontSize: 13, fontFamily: 'Outfit_400Regular', marginTop: 4 },
|
||||
|
||||
list: { paddingHorizontal: 24 },
|
||||
notifPressable: { marginBottom: 10 },
|
||||
card: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
padding: 16,
|
||||
borderRadius: 20,
|
||||
borderWidth: 1,
|
||||
},
|
||||
iconBox: {
|
||||
width: 48, height: 48, borderRadius: 14,
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
marginRight: 14, flexShrink: 0,
|
||||
},
|
||||
body: { flex: 1 },
|
||||
topRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 },
|
||||
notifTitle: { fontSize: 15, fontFamily: 'Outfit_700Bold', flex: 1, marginRight: 8 },
|
||||
time: { fontSize: 11, fontFamily: 'Outfit_500Medium', flexShrink: 0, marginTop: 1 },
|
||||
desc: { fontSize: 13, fontFamily: 'Outfit_400Regular', lineHeight: 18 },
|
||||
});
|
||||
Reference in New Issue
Block a user