import { Text, View, RefreshControl, ActivityIndicator, Dimensions, ScrollView, TextInput, } from "react-native"; import React, {useState, useEffect} from "react"; import {FlatList, TouchableOpacity} from "react-native-gesture-handler"; import Snackbar from "react-native-snackbar"; import {getStr} from "src/utils/i18n"; import {helper} from "../../redux/store"; import {RootNav} from "../../components/Root"; import themes from "../../assets/themes/themes"; import { NewsSlice, SourceTag, sourceTags, } from "thu-info-lib/dist/models/news/news"; import {useColorScheme} from "react-native"; import themedStyles from "../../utils/themedStyles"; import {SettingsLargeButton} from "../../components/settings/items"; const ChannelTag = ({ channel, selected, onPress, }: { channel: SourceTag | undefined; selected: boolean; onPress: () => void; }) => { const themeName = useColorScheme(); const {colors} = themes(themeName); return ( <TouchableOpacity style={{alignItems: "center", marginHorizontal: 6}} onPress={onPress} disabled={selected}> <Text style={{ fontSize: 15, color: selected ? colors.primaryLight : colors.text, }}> {getStr(channel ?? "all")} </Text> <View style={{ height: 2, width: 12, borderRadius: 1, margin: 2, backgroundColor: selected ? colors.primaryLight : undefined, }} /> </TouchableOpacity> ); }; export const NewsScreen = ({navigation}: {navigation: RootNav}) => { const [newsList, setNewsList] = useState<NewsSlice[]>([]); const [refreshing, setRefreshing] = useState(true); const [loading, setLoading] = useState(false); const [page, setPage] = useState(1); const [inSearchMode, setInSearchMode] = useState(false); const [searchKey, setSearchKey] = useState(""); const [channel, setChannel] = useState<SourceTag | undefined>(); const [fetchedAll, setFetchedAll] = useState(false); const themeName = useColorScheme(); const theme = themes(themeName); const style = styles(themeName); const fetchNewsList = ( request: boolean = true, searchMode: boolean | undefined = undefined, ) => { setRefreshing(true); setLoading(true); if (request) { setNewsList([]); setPage(1); setFetchedAll(false); if (searchMode === undefined) { setInSearchMode(false); setSearchKey(""); } else { setInSearchMode(searchMode); } } else { if (fetchedAll) { setRefreshing(false); setLoading(false); return; } setPage((p) => p + 1); } (searchMode === true || (searchMode === undefined && !request && inSearchMode) ? helper.searchNewsList(request ? 1 : page + 1, searchKey, channel) : helper.getNewsList(request ? 1 : page + 1, 30, channel) ) .then((res) => { if (res.length === 0) { setFetchedAll(true); } else { setNewsList((o) => o.concat(res)); } }) .catch(() => { Snackbar.show({ text: getStr("networkRetry"), duration: Snackbar.LENGTH_LONG, }); }) .then(() => { setRefreshing(false); setLoading(false); }); }; // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(fetchNewsList, [channel]); let screenHeight = Dimensions.get("window"); const flatListRef = React.useRef(null); return ( <View style={{marginHorizontal: 12}}> <ScrollView style={{margin: 6}} showsHorizontalScrollIndicator={false} horizontal={true}> <ChannelTag channel={undefined} selected={channel === undefined} onPress={() => setChannel(undefined)} /> {sourceTags.map((tag) => ( <ChannelTag key={tag} channel={tag} selected={channel === tag} onPress={() => setChannel(tag)} /> ))} </ScrollView> <FlatList ref={flatListRef} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={fetchNewsList} colors={[theme.colors.accent]} /> } ListHeaderComponent={ <View style={{flexDirection: "row"}}> <TextInput value={searchKey} onChangeText={setSearchKey} style={{ flex: 3, marginLeft: 12, textAlignVertical: "center", fontSize: 15, paddingHorizontal: 12, backgroundColor: theme.colors.themeBackground, color: theme.colors.text, borderColor: "#CCC", borderWidth: 1, borderRadius: 5, }} placeholder={getStr("searchNewsPrompt")} /> <SettingsLargeButton text={getStr("search")} onPress={() => { fetchNewsList(true, searchKey !== ""); }} disabled={refreshing || loading} redText={false} /> </View> } ListEmptyComponent={ <View style={{ margin: 15, height: screenHeight.height * 0.6, justifyContent: "center", alignItems: "center", }}> <Text style={{ fontSize: 18, fontWeight: "bold", alignSelf: "center", margin: 5, color: theme.colors.text, }}> {getStr("waitForLoading")} </Text> </View> } data={newsList} keyExtractor={(item) => item.url} renderItem={({item}) => ( <View style={style.newsSliceContainer}> <TouchableOpacity onPress={() => navigation.navigate("NewsDetail", {detail: item})}> <Text numberOfLines={2} style={{ fontSize: 16, fontWeight: "bold", margin: 5, lineHeight: 20, color: theme.colors.text, }}> {item.name.trim()} </Text> <View style={{margin: 5, flexDirection: "row", alignItems: "center"}}> {item.source.length > 0 && ( <> <Text style={{fontWeight: "bold", color: theme.colors.text}}> {item.source} </Text> <View style={{ marginHorizontal: 6, height: 12, width: 4, borderRadius: 2, backgroundColor: theme.colors.accent, }} /> </> )} <Text style={{fontWeight: "bold", color: theme.colors.text}}> {getStr(item.channel)} </Text> </View> <Text style={{color: "gray", margin: 5}}> {item.date} {item.topped && ( <Text style={{color: "red"}}> {" "} {getStr("topped")} </Text> )} </Text> </TouchableOpacity> </View> )} onEndReached={() => fetchNewsList(false)} onEndReachedThreshold={0.6} ListFooterComponent={ loading && newsList.length !== 0 ? ( <View style={style.footerContainer}> <ActivityIndicator size="small" /> <Text style={{margin: 10, color: theme.colors.text}}> {getStr("loading")} </Text> </View> ) : null } /> </View> ); }; const styles = themedStyles(({colors}) => ({ newsSliceContainer: { backgroundColor: colors.contentBackground, justifyContent: "center", padding: 6, marginVertical: 6, borderRadius: 5, }, footerContainer: { flexDirection: "row", alignSelf: "stretch", height: 80, justifyContent: "center", alignItems: "center", }, }));