guardia-messenger/app/(tabs)/release_notes.tsx

88 lines
3.7 KiB
TypeScript

import React, { useState, useCallback } from 'react'
import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native'
import Constants from 'expo-constants'
import { useFocusEffect } from 'expo-router'
import { COLORS } from '../../constants/Config'
import { getReleaseNotes } from '../../services/api'
export default function ReleaseNotesScreen() {
const [notes, setNotes] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const [expanded, setExpanded] = useState<Set<string>>(new Set())
const load = useCallback(async () => {
setLoading(true)
try { const r = await getReleaseNotes(); setNotes(r.data?.items ?? r.data ?? []) }
catch { setNotes([]) }
finally { setLoading(false) }
}, [])
useFocusEffect(useCallback(() => { load() }, [load]))
const toggle = (v: string) => {
const next = new Set(expanded)
next.has(v) ? next.delete(v) : next.add(v)
setExpanded(next)
}
const appVersion = Constants.expoConfig?.version ?? '1.0.0'
return (
<View style={s.container}>
<View style={s.header}>
<Text style={s.headerTitle}> </Text>
<Text style={s.appVer}> : {appVersion}</Text>
</View>
<FlatList
data={notes}
keyExtractor={(_, i) => String(i)}
refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />}
ListEmptyComponent={<Text style={s.empty}> .</Text>}
contentContainerStyle={{ padding: 12 }}
renderItem={({ item, index }) => {
const v = item.version ?? `v${index + 1}`
const isOpen = expanded.has(v)
return (
<TouchableOpacity style={s.card} onPress={() => toggle(v)}>
<View style={s.row}>
<View style={s.rowLeft}>
{index === 0 && <View style={s.newBadge}><Text style={s.newBadgeText}>NEW</Text></View>}
<Text style={s.version}>{v}</Text>
</View>
<Text style={s.date}>{item.released_at?.slice(0, 10) ?? '-'}</Text>
<Text style={s.arrow}>{isOpen ? '▲' : '▼'}</Text>
</View>
{isOpen && (
<View style={s.body}>
{(item.changes ?? item.items ?? [item.description ?? '']).map((c: string, i: number) => (
<Text key={i} style={s.change}> {c}</Text>
))}
</View>
)}
</TouchableOpacity>
)
}}
/>
</View>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
header: { backgroundColor: COLORS.primary, padding: 16, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
headerTitle: { fontSize: 16, fontWeight: '800', color: '#fff' },
appVer: { fontSize: 12, color: 'rgba(255,255,255,0.7)' },
card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 },
row: { flexDirection: 'row', alignItems: 'center', gap: 8 },
rowLeft: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: 6 },
newBadge: { backgroundColor: COLORS.danger, borderRadius: 4, paddingHorizontal: 5, paddingVertical: 2 },
newBadgeText: { fontSize: 9, color: '#fff', fontWeight: '800' },
version: { fontSize: 15, fontWeight: '700', color: COLORS.text },
date: { fontSize: 12, color: COLORS.muted },
arrow: { fontSize: 12, color: COLORS.muted },
body: { marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: COLORS.border },
change: { fontSize: 13, color: COLORS.text, lineHeight: 22 },
empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 },
})