Spaces:
Running
Running
import React, { useEffect, useState, useCallback, useContext, useRef } from 'react'; | |
import { Link, router } from 'expo-router'; | |
import Image from '@/components/Image'; | |
import { StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl } from 'react-native'; | |
import { SafeAreaView } from 'react-native-safe-area-context'; | |
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper'; | |
import CountryFlag from "react-native-country-flag"; | |
import Dropdown from '@/components/dropdown'; | |
import Theme from '@/constants/theme'; | |
import { __styles } from './stylesheet/show_list_styles'; | |
import Storage from '@/constants/module/storage'; | |
import ImageStorage from '@/constants/module/image_cache_storage'; | |
import { CONTEXT } from '@/constants/module/context'; | |
import { get_list } from '@/app/explore/module/content' | |
import { transformAsync } from '@babel/core'; | |
import { View, AnimatePresence } from 'moti'; | |
import { PageNavigationWidget } from './components/widgets'; | |
const Index = ({}:any) => { | |
const Dimensions = useWindowDimensions(); | |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT) | |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT) | |
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT) | |
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT) | |
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT) | |
const [styles, setStyles]:any = useState("") | |
const [CONTENT, SET_CONTENT]:any = useState([]) | |
const [isLoading, setIsLoading]:any = useState(true); | |
const [refreshing, setRefreshing] = useState(false); | |
const [feedBack, setFeedBack] = useState("") | |
const scrollOffset = useRef(0); | |
const [showOption, setShowOption]:any = useState({type:null}) | |
const [translate, setTranslate]:any = useState({}); | |
const [search, setSearch]:any = useState({text:"",type:1}); | |
const [source, setSource]:any = useState("colamanga") | |
const [page, setPage] = useState(1); | |
const controller = new AbortController(); | |
const signal = controller.signal; | |
useEffect(() => { | |
(async ()=>{ | |
setStyles(__styles(themeTypeContext,Dimensions)) | |
let __translate:any = await Storage.get("explore_translate") | |
if (!__translate) { | |
__translate = {state:false,from:"auto",to:"en"} | |
await Storage.store("explore_translate",__translate) | |
}else __translate = __translate | |
setTranslate(__translate) | |
get_list(setShowCloudflareTurnstileContext,setFeedBack,signal,setIsLoading,__translate,SET_CONTENT,search,page) | |
})() | |
return () => { | |
controller.abort(); | |
}; | |
},[]) | |
const onRefresh = () => { | |
if (!(styles && themeTypeContext && apiBaseContext)) return | |
setShowMenuContext(true) | |
setIsLoading(true); | |
SET_CONTENT([]) | |
get_list(setShowCloudflareTurnstileContext,setFeedBack,signal,setIsLoading,translate,SET_CONTENT,search,page) | |
} | |
useEffect(()=>{ | |
onRefresh() | |
},[page]) | |
const onScroll = useCallback((event:any) => { | |
const nativeEvent = event.nativeEvent | |
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent; | |
var currentOffset = event.nativeEvent.contentOffset.y; | |
var direction = currentOffset > scrollOffset.current ? 'down' : 'up'; | |
scrollOffset.current = currentOffset; | |
if (direction === 'down') { | |
if (contentOffset.y <= contentSize.height*0.025) { | |
setShowMenuContext(true) | |
}else{ | |
setShowMenuContext(false) | |
} | |
} | |
else { | |
if (layoutMeasurement.height + contentOffset.y >= (contentSize.height - contentSize.height*0.025)) { | |
setShowMenuContext(false) | |
}else{ | |
setShowMenuContext(true) | |
} | |
} | |
},[]) | |
return (<>{(styles && !isLoading) ? <> | |
<ScrollView style={styles.screen_container} | |
refreshControl={ | |
<RefreshControl refreshing={refreshing} onRefresh={()=>{ | |
if (!isLoading) onRefresh() | |
}} /> | |
} | |
onScroll={(event) => {onScroll(event)}} | |
scrollEventThrottle={5} | |
> | |
<View style={styles.header_container}> | |
<Text style={styles.header_text}>Explore</Text> | |
<View style={styles.header_button_box}> | |
<TouchableRipple | |
rippleColor={Theme[themeTypeContext].ripple_color_outlined} | |
style={{ | |
...styles.header_translate_button, | |
backgroundColor: showOption.type === "translate" ? Theme[themeTypeContext].button_selected_color : "transparent" | |
}} | |
onPress={() => { | |
if (showOption.type === "translate"){ | |
setShowOption({type:null}) | |
}else{ | |
setShowOption({type:"translate"}) | |
} | |
}} | |
> | |
<Icon source={translate.state ? "translate" : "translate-off"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/> | |
</TouchableRipple> | |
<TouchableRipple | |
rippleColor={Theme[themeTypeContext].ripple_color_outlined} | |
style={{ | |
...styles.header_search_button, | |
backgroundColor: showOption.type === "search" ? Theme[themeTypeContext].button_selected_color : "transparent" | |
}} | |
onPress={() => { | |
if (showOption.type === "search"){ | |
setShowOption({type:null}) | |
}else{ | |
setShowOption({type:"search"}) | |
} | |
}} | |
> | |
<Icon source={"magnify"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/> | |
</TouchableRipple> | |
<TouchableRipple | |
rippleColor={Theme[themeTypeContext].ripple_color_outlined} | |
disabled={isLoading} | |
style={ | |
styles.header_refresh_button | |
} | |
onPress={onRefresh} | |
> | |
<Icon color={Theme[themeTypeContext].icon_color} source={"refresh"} size={((Dimensions.width+Dimensions.height)/2)*0.04}/> | |
</TouchableRipple> | |
</View> | |
</View> | |
<AnimatePresence> | |
{showOption.type === "translate" && | |
<View style={styles.option_container} key={"translate"} | |
from={{ | |
opacity: 0, | |
scale: 0.9, | |
}} | |
animate={{ | |
opacity: 1, | |
scale: 1, | |
}} | |
exit={{ | |
opacity: 0, | |
scale: 0.5, | |
}} | |
transition={{ | |
type: 'timing', | |
duration: 500, | |
}} | |
exitTransition={{ | |
type: 'timing', | |
duration: 250, | |
}} | |
> | |
<View style={{ | |
display:"flex", | |
flexDirection:"row", | |
width:"100%", | |
justifyContent:"space-around", | |
gap:25, | |
}}> | |
<View style={{flexGrow:1,}}> | |
<Dropdown | |
theme_type={themeTypeContext} | |
Dimensions={Dimensions} | |
label='From Language' | |
data={[ | |
{ | |
label: "Auto", | |
value: 'auto' | |
}, | |
{ | |
label: "Chinese", | |
value: 'zh' | |
}, | |
]} | |
value={translate.from} | |
onChange={async (item:any) => { | |
setTranslate({...translate,from:item.value}) | |
await Storage.store("explore_translate",{...translate,from:item.value}) | |
}} | |
/> | |
</View> | |
<View style={{flexGrow:1,}}> | |
<Dropdown | |
theme_type={themeTypeContext} | |
Dimensions={Dimensions} | |
label='To Language' | |
data={[ | |
{ | |
label: "English", | |
value: 'en' | |
}, | |
]} | |
value={translate.to} | |
onChange={async (item:any) => { | |
setTranslate({...translate,to:item.value}) | |
await Storage.store("explore_translate",{...translate,to:item.value}) | |
}} | |
/> | |
</View> | |
</View> | |
<View style={{ | |
width:"100%", | |
display:"flex", | |
alignItems:"center", | |
justifyContent:"center", | |
flexDirection:"row", | |
}}> | |
<Button mode={"contained"} style={{ | |
width:"auto", | |
borderRadius:8, | |
backgroundColor: translate.state ? "red": "green", | |
}} | |
onPress={async () => { | |
if (translate.state){ | |
setTranslate({...translate,state:false}) | |
await Storage.store("explore_translate",{...translate,state:false}) | |
}else{ | |
setTranslate({...translate,state:true}) | |
await Storage.store("explore_translate",{...translate,state:true}) | |
} | |
}} | |
> | |
{translate.state ? "Disable Translation" : "Enable Translation"} | |
</Button> | |
</View> | |
</View> | |
} | |
{showOption.type === "search" && | |
<View style={styles.option_container} key={"search"} | |
from={{ | |
opacity: 0, | |
scale: 0.9, | |
}} | |
animate={{ | |
opacity: 1, | |
scale: 1, | |
}} | |
exit={{ | |
opacity: 0, | |
scale: 0.5, | |
}} | |
transition={{ | |
type: 'timing', | |
duration: 500, | |
}} | |
exitTransition={{ | |
type: 'timing', | |
duration: 250, | |
}} | |
> | |
<View style={{ | |
display:"flex", | |
flexDirection: Dimensions.width <= 720 ? "column" : "row", | |
width:"100%", | |
alignItems:"center", | |
justifyContent:"center", | |
gap:25, | |
}}> | |
<View style={{flex:1,width:"100%"}}> | |
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color} | |
placeholder="Tip: search by using the original language for better results" | |
style={{ | |
backgroundColor:Theme[themeTypeContext].background_color, | |
borderColor:Theme[themeTypeContext].border_color, | |
}} | |
outlineColor={Theme[themeTypeContext].text_input_border_color} | |
value={search.text} | |
onChange={(event)=>{ | |
setSearch({...search,text:event.nativeEvent.text}) | |
}} | |
/> | |
</View> | |
<View | |
style={{ | |
display:"flex", | |
flexDirection:Dimensions.width <= 720 ? "row-reverse" : "row", | |
gap:25, | |
justifyContent:"center", | |
alignItems:"center", | |
}}> | |
<Button mode="contained" disabled={isLoading} | |
style={{ | |
borderRadius:5, | |
backgroundColor: "purple", | |
}} | |
labelStyle={{fontFamily:"roboto-medium",fontSize:(Dimensions.width+Dimensions.height)/2*0.0220}} | |
onPress={()=>{ | |
onRefresh() | |
}} | |
> | |
Search | |
</Button> | |
<View style={{flexGrow:1}}> | |
<Dropdown | |
theme_type={themeTypeContext} | |
Dimensions={Dimensions} | |
label='Search Type' | |
data={[ | |
{ | |
label: "Obscure ", | |
value: 1 | |
}, | |
{ | |
label: "Precise", | |
value: 2 | |
}, | |
]} | |
value={search.type} | |
onChange={(item:any) => { | |
setSearch({...search,type:item.value}) | |
}} | |
/> | |
</View> | |
</View> | |
</View> | |
</View> | |
} | |
</AnimatePresence> | |
<View style={styles.body_container}> | |
<View style={styles.content_container}> | |
{feedBack | |
? <View | |
style={{ | |
width:"100%", | |
height:"auto", | |
display:"flex", | |
justifyContent:"center", | |
alignItems:"center", | |
padding:12, | |
paddingTop:20, | |
}} | |
> | |
<Text | |
style={{ | |
color:Theme[themeTypeContext].text_color, | |
fontFamily:"roboto-medium", | |
fontSize:(Dimensions.width+Dimensions.height)/2*0.03 | |
}} | |
>{feedBack}</Text> | |
</View> | |
: <> | |
{CONTENT.map((item:any,index:number)=>( | |
<TouchableRipple | |
rippleColor={Theme[themeTypeContext].ripple_color_outlined} | |
onPress={()=>{router.push(`/view/${source}/${item.id}`)}} key={index} | |
style={styles.item_box} | |
> | |
<> | |
<Image onError={(error:any)=>{console.log("load image error",error)}} source={{uri:`${apiBaseContext}${item.cover}`}} style={styles.item_cover} | |
contentFit="cover" transition={1000} | |
/> | |
<Text style={styles.item_title}>{item.title}</Text> | |
</> | |
</TouchableRipple> | |
))} | |
</> | |
} | |
</View> | |
{CONTENT.length | |
? <View | |
style={{ | |
display:"flex", | |
flexDirection:"row", | |
justifyContent:"center", | |
gap:8, | |
padding:12, | |
}} | |
> | |
<Button mode='outlined' | |
labelStyle={{ | |
color:Theme[themeTypeContext].text_color, | |
fontFamily:"roboto-medium", | |
fontSize:(Dimensions.width+Dimensions.height)/2*0.03 | |
}} | |
style={{borderWidth:0}} | |
onPress={(()=>{ | |
if (page === 1) return | |
setPage((page:number) => page-1) | |
} | |
)} | |
>{"<"}</Button> | |
<Button mode='outlined' | |
labelStyle={{ | |
color:Theme[themeTypeContext].text_color, | |
fontFamily:"roboto-medium", | |
fontSize:(Dimensions.width+Dimensions.height)/2*0.02 | |
}} | |
style={{ | |
borderRadius:8, | |
borderColor:Theme[themeTypeContext].border_color, | |
}} | |
onPress={(()=>{ | |
setWidgetContext({state:true,component:<PageNavigationWidget setPage={setPage}/>}) | |
})} | |
>{page}</Button> | |
<Button mode='outlined' | |
labelStyle={{ | |
color:Theme[themeTypeContext].text_color, | |
fontFamily:"roboto-medium", | |
fontSize:(Dimensions.width+Dimensions.height)/2*0.03 | |
}} | |
style={{borderWidth:0}} | |
onPress={(()=>{ | |
setPage((page:number) => page+1) | |
})} | |
>{">"}</Button> | |
</View> | |
: <></> | |
} | |
</View> | |
</ScrollView> | |
</> | |
: <View style={{zIndex:5,width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}> | |
<Image setShowCloudflareTurnstile={setShowCloudflareTurnstileContext} source={require("@/assets/gif/cat-loading.gif")} style={{width:((Dimensions.width+Dimensions.height)/2)*0.15,height:((Dimensions.width+Dimensions.height)/2)*0.15}}/> | |
</View> | |
}</>); | |
} | |
export default Index; | |