├── assets ├── icon.png ├── favicon.png ├── splash.png ├── logo │ └── logo.png ├── adaptive-icon.png └── fonts │ ├── Poppins-Black.ttf │ ├── Poppins-Bold.ttf │ ├── Poppins-Italic.ttf │ ├── Poppins-Light.ttf │ ├── Poppins-Medium.ttf │ ├── Poppins-Thin.ttf │ ├── LobsterTwo-Bold.ttf │ ├── Poppins-Regular.ttf │ ├── Poppins-SemiBold.ttf │ ├── LobsterTwo-Italic.ttf │ ├── LobsterTwo-Regular.ttf │ ├── Poppins-BlackItalic.ttf │ ├── Poppins-BoldItalic.ttf │ ├── Poppins-ExtraBold.ttf │ ├── Poppins-ExtraLight.ttf │ ├── Poppins-LightItalic.ttf │ ├── Poppins-ThinItalic.ttf │ ├── LobsterTwo-BoldItalic.ttf │ ├── Poppins-MediumItalic.ttf │ ├── Poppins-ExtraBoldItalic.ttf │ ├── Poppins-ExtraLightItalic.ttf │ ├── Poppins-SemiBoldItalic.ttf │ └── OFL.txt ├── config └── styles.js ├── .expo-shared └── assets.json ├── .gitignore ├── screenshot ├── Screenshot_2022-09-04-21-03-35-76_d06dc5dcfcdb6001e9885a098fe0365c.jpg ├── Screenshot_2022-09-04-21-03-49-87_d06dc5dcfcdb6001e9885a098fe0365c.jpg ├── Screenshot_2022-09-04-21-04-20-29_d06dc5dcfcdb6001e9885a098fe0365c.jpg ├── Screenshot_2022-09-04-21-33-13-10_d06dc5dcfcdb6001e9885a098fe0365c.jpg └── Screenshot_2022-09-04-21-33-43-38_d06dc5dcfcdb6001e9885a098fe0365c.jpg ├── babel.config.js ├── screens ├── Genre.js ├── Watchlist.js ├── Search.js ├── Home.js ├── Episodewatch.js └── Animedetails.js ├── eas.json ├── utils ├── storage.js └── data.js ├── component ├── WatchHistory.js ├── Title.js └── Card.js ├── navigation └── islandNav.js ├── app.json ├── README.md ├── package.json └── App.js /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/logo/logo.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /config/styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | colors:{ 3 | accentColor: "#FFAF19", 4 | 5 | } 6 | } -------------------------------------------------------------------------------- /assets/fonts/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Thin.ttf -------------------------------------------------------------------------------- /assets/fonts/LobsterTwo-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/LobsterTwo-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/LobsterTwo-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/LobsterTwo-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/LobsterTwo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/LobsterTwo-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/LobsterTwo-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/LobsterTwo-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/assets/fonts/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | release/ 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /screenshot/Screenshot_2022-09-04-21-03-35-76_d06dc5dcfcdb6001e9885a098fe0365c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/screenshot/Screenshot_2022-09-04-21-03-35-76_d06dc5dcfcdb6001e9885a098fe0365c.jpg -------------------------------------------------------------------------------- /screenshot/Screenshot_2022-09-04-21-03-49-87_d06dc5dcfcdb6001e9885a098fe0365c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/screenshot/Screenshot_2022-09-04-21-03-49-87_d06dc5dcfcdb6001e9885a098fe0365c.jpg -------------------------------------------------------------------------------- /screenshot/Screenshot_2022-09-04-21-04-20-29_d06dc5dcfcdb6001e9885a098fe0365c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/screenshot/Screenshot_2022-09-04-21-04-20-29_d06dc5dcfcdb6001e9885a098fe0365c.jpg -------------------------------------------------------------------------------- /screenshot/Screenshot_2022-09-04-21-33-13-10_d06dc5dcfcdb6001e9885a098fe0365c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/screenshot/Screenshot_2022-09-04-21-33-13-10_d06dc5dcfcdb6001e9885a098fe0365c.jpg -------------------------------------------------------------------------------- /screenshot/Screenshot_2022-09-04-21-33-43-38_d06dc5dcfcdb6001e9885a098fe0365c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6ixline/Animasi/HEAD/screenshot/Screenshot_2022-09-04-21-33-43-38_d06dc5dcfcdb6001e9885a098fe0365c.jpg -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | 'react-native-reanimated/plugin', 7 | ], 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /screens/Genre.js: -------------------------------------------------------------------------------- 1 | import { View, Text } from 'react-native' 2 | import React from 'react' 3 | 4 | const Genre = () => { 5 | return ( 6 | 7 | Genre 8 | 9 | ) 10 | } 11 | 12 | export default Genre -------------------------------------------------------------------------------- /screens/Watchlist.js: -------------------------------------------------------------------------------- 1 | import { View, Text } from 'react-native' 2 | import React from 'react' 3 | 4 | const Watchlist = () => { 5 | return ( 6 | 7 | Watchlist 8 | 9 | ) 10 | } 11 | 12 | export default Watchlist -------------------------------------------------------------------------------- /eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 0.60.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "android": { 12 | "buildType": "apk" 13 | } 14 | }, 15 | "production": {} 16 | }, 17 | "submit": { 18 | "production": {} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /utils/storage.js: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | 3 | export const storeData = async (value, key) => { 4 | try { 5 | const jsonValue = JSON.stringify(value) 6 | await AsyncStorage.setItem(key, jsonValue) 7 | } catch (e) { 8 | // saving error 9 | } 10 | } 11 | 12 | export const getData = async (key) => { 13 | try { 14 | const jsonValue = await AsyncStorage.getItem(key) 15 | return jsonValue != null ? JSON.parse(jsonValue) : null; 16 | } catch(e) { 17 | return null; 18 | } 19 | } -------------------------------------------------------------------------------- /component/WatchHistory.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, FlatList } from 'react-native' 2 | import React from 'react' 3 | import Title from './Title' 4 | import Card from './Card' 5 | 6 | const WatchHistory = ({data, animeHandle}) => { 7 | return ( 8 | <> 9 | 10 | <FlatList 11 | data={data} 12 | renderItem={({item})=>( 13 | <Card item={item} animeHandle={animeHandle} /> 14 | )} 15 | keyExtractor={(item)=> item.id} 16 | showsVerticalScrollIndicator={false} 17 | horizontal={true} 18 | /> 19 | </> 20 | ) 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | 25 | }) 26 | 27 | export default WatchHistory -------------------------------------------------------------------------------- /navigation/islandNav.js: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet, Pressable } from 'react-native' 2 | import React from 'react' 3 | 4 | const IslandNav = () => { 5 | return ( 6 | <Pressable style={styles.mainContainer}> 7 | <View style={styles.innerContainer}> 8 | <Text>islandNaI</Text> 9 | </View> 10 | </Pressable> 11 | ) 12 | } 13 | 14 | const styles = StyleSheet.create({ 15 | mainContainer:{ 16 | position: 'absolute', 17 | bottom: 20, 18 | zIndex: 999, 19 | width: "100%", 20 | height: 60, 21 | justifyContent: 'center', 22 | alignItems: 'center' 23 | }, 24 | innerContainer:{ 25 | flex: 1, 26 | width: "60%", 27 | backgroundColor: '#494F55', 28 | borderRadius: 80, 29 | elevation: 5, 30 | opacity: 0.9 31 | } 32 | }) 33 | 34 | export default IslandNav; -------------------------------------------------------------------------------- /component/Title.js: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet } from 'react-native' 2 | import React from 'react' 3 | import themeStyles from "../config/styles"; 4 | import Ionicons from '@expo/vector-icons/Ionicons'; 5 | 6 | const Title = ({boldTitle, NormalTitle, icon}) => { 7 | return ( 8 | <View style={styles.catContainer}> 9 | <Ionicons name={icon} size={18} color={themeStyles.colors.accentColor} /> 10 | <Text style={styles.catTitle}>{boldTitle} <Text style={{fontFamily: "pop-regular"}}>{NormalTitle}</Text></Text> 11 | </View> 12 | ) 13 | } 14 | 15 | const styles = StyleSheet.create({ 16 | catContainer:{ 17 | marginVertical: 20, 18 | marginHorizontal: 10, 19 | flexDirection: 'row', 20 | alignItems: "center" 21 | }, 22 | catTitle:{ 23 | fontSize: 18, 24 | color: "#fff", 25 | fontFamily: "pop-medium", 26 | marginHorizontal: 10 27 | } 28 | }) 29 | 30 | export default Title -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Animasi", 4 | "slug": "Animasi", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "backgroundColor": "#000", 10 | "splash": { 11 | "image": "./assets/splash.png", 12 | "resizeMode": "cover", 13 | "backgroundColor": "#FFAF19" 14 | }, 15 | "updates": { 16 | "fallbackToCacheTimeout": 0 17 | }, 18 | "assetBundlePatterns": [ 19 | "**/*" 20 | ], 21 | "ios": { 22 | "supportsTablet": true 23 | }, 24 | "android": { 25 | "adaptiveIcon": { 26 | "foregroundImage": "./assets/adaptive-icon.png", 27 | "backgroundColor": "#FFAF19" 28 | }, 29 | "package": "com.devsourabh.Animasi" 30 | }, 31 | "web": { 32 | "favicon": "./assets/favicon.png" 33 | }, 34 | "extra": { 35 | "eas": { 36 | "projectId": "4837097f-1e2e-45f5-b850-b02407fc7218" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <p align="center"> 2 | <img alt="Animasi" src="https://github.com/6ixline/Animasi/blob/main/assets/logo/logo.png?raw=true" width="150"> 3 | </p> 4 | 5 | <p align="center"> 6 | React native App for watching anime built with consumet api 7 | </p> 8 | 9 | ## Screenshots 10 | <img alt="Splash" src="https://github.com/6ixline/Animasi/blob/main/screenshot/Screenshot_2022-09-04-21-33-13-10_d06dc5dcfcdb6001e9885a098fe0365c.jpg?raw=true" width="165"> <img alt="Home" src="https://github.com/6ixline/Animasi/blob/main/screenshot/Screenshot_2022-09-04-21-03-35-76_d06dc5dcfcdb6001e9885a098fe0365c.jpg?raw=true" width="165"> <img alt="Search" src="https://github.com/6ixline/Animasi/blob/main/screenshot/Screenshot_2022-09-04-21-04-20-29_d06dc5dcfcdb6001e9885a098fe0365c.jpg?raw=true" width="165"> <img alt="Details Page" src="https://github.com/6ixline/Animasi/blob/main/screenshot/Screenshot_2022-09-04-21-03-49-87_d06dc5dcfcdb6001e9885a098fe0365c.jpg?raw=true" width="165"> <img alt="Episode Watch" src="https://github.com/6ixline/Animasi/blob/main/screenshot/Screenshot_2022-09-04-21-33-43-38_d06dc5dcfcdb6001e9885a098fe0365c.jpg?raw=true" width="165"> 11 | -------------------------------------------------------------------------------- /component/Card.js: -------------------------------------------------------------------------------- 1 | import { Text, TouchableOpacity, Image, StyleSheet } from 'react-native' 2 | import React from 'react' 3 | 4 | const Card = ({item, animeHandle, cardStyle}) => { 5 | let length = Object.keys(item).length; //0 6 | let title = ''; 7 | if(length != 0){ 8 | title = item.title?.userPreferred && item.title.userPreferred; 9 | if(title == null){ 10 | title = item.title.english && item.title.english 11 | } 12 | } 13 | return ( 14 | <TouchableOpacity style={[styles.watchcardBody, cardStyle]} onPress={()=>animeHandle(item.id)}> 15 | <Image style={styles.cardImage} source={{uri: item.image}}/> 16 | <Text style={styles.cardTitle}>{`${title.substr(0,12)}`}{title.length > 12 && "..."}</Text> 17 | </TouchableOpacity> 18 | ) 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | watchcardBody:{ 23 | marginHorizontal: 8, 24 | elevation: 4, 25 | width: 120 26 | }, 27 | cardImage:{ 28 | width: "100%", 29 | height: 120, 30 | borderRadius: 10, 31 | }, 32 | cardTitle:{ 33 | margin: 2, 34 | textAlign: 'center', 35 | fontSize: 14, 36 | fontFamily: "pop-medium", 37 | padding: 5, 38 | color: "#fff", 39 | }, 40 | }) 41 | 42 | export default Card -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "animasi", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web", 10 | "build": "eas build -p android --profile preview" 11 | }, 12 | "dependencies": { 13 | "@react-native-async-storage/async-storage": "~1.17.3", 14 | "@react-native-community/slider": "4.2.3", 15 | "@react-navigation/bottom-tabs": "^6.3.3", 16 | "@react-navigation/native": "^6.0.12", 17 | "@react-navigation/native-stack": "^6.8.0", 18 | "axios": "^0.27.2", 19 | "deprecated-react-native-prop-types": "^2.3.0", 20 | "expo": "~46.0.9", 21 | "expo-app-loading": "~2.1.0", 22 | "expo-av": "~12.0.4", 23 | "expo-dev-client": "~1.2.1", 24 | "expo-linear-gradient": "~11.4.0", 25 | "expo-screen-orientation": "~4.3.0", 26 | "expo-status-bar": "~1.4.0", 27 | "expo-video-player": "^2.1.0", 28 | "react": "18.0.0", 29 | "react-native": "0.69.5", 30 | "react-native-anchor-carousel": "^4.0.1", 31 | "react-native-animated-nav-tab-bar": "^3.1.8", 32 | "react-native-gesture-handler": "^2.7.1", 33 | "react-native-read-more-text": "^1.1.2", 34 | "react-native-reanimated": "^2.11.0", 35 | "react-native-reanimated-carousel": "^3.0.6", 36 | "react-native-safe-area-context": "4.3.1", 37 | "react-native-screens": "~3.15.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.12.9" 41 | }, 42 | "private": true 43 | } 44 | -------------------------------------------------------------------------------- /utils/data.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const apiUrl = "https://api.consumet.org/"; 4 | 5 | export async function getpopularAnime(){ 6 | try { 7 | const data = await axios.get(`${apiUrl}meta/anilist/popular`); 8 | return data.data.results; 9 | } catch (error) { 10 | return []; 11 | } 12 | } 13 | export async function getTrendingAnime(){ 14 | try { 15 | const data = await axios.get(`${apiUrl}meta/anilist/trending?perPage=24`); 16 | return data.data.results; 17 | } catch (error) { 18 | return []; 19 | } 20 | } 21 | export async function recentEpisode(){ 22 | try { 23 | const data = await axios.get(`${apiUrl}meta/anilist/recent-episodes`); 24 | return data.data.results; 25 | } catch (error) { 26 | return []; 27 | } 28 | } 29 | 30 | export async function searchEpisode(query){ 31 | try { 32 | const data = await axios.get(`${apiUrl}meta/anilist/advanced-search?query=${query}&perPage=16`); 33 | return data.data.results; 34 | } catch (error) { 35 | return []; 36 | } 37 | } 38 | export async function getAnimeInfo(id, dub = false){ 39 | try { 40 | const data = await axios.get(`${apiUrl}meta/anilist/info/${id}?provider=gogoanime&dub=${dub}`); 41 | return data.data; 42 | } catch (error) { 43 | return []; 44 | } 45 | } 46 | 47 | const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36"; 48 | export async function getAnimeVideoLink(id){ 49 | try { 50 | const data = await axios.get(`${apiUrl}meta/anilist/watch/${id}`, { 51 | headers: { 52 | 'User-Agent': USER_AGENT, 53 | 'X-Requested-With': 'XMLHttpRequest', 54 | }, 55 | }); 56 | return data.data; 57 | } catch (error) { 58 | return []; 59 | } 60 | } -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import { NavigationContainer } from "@react-navigation/native"; 4 | import { createNativeStackNavigator } from '@react-navigation/native-stack'; 5 | import { GestureHandlerRootView } from 'react-native-gesture-handler'; 6 | import { useFonts } from "expo-font"; 7 | import AppLoading from 'expo-app-loading'; 8 | import Home from './screens/Home'; 9 | import Animedetails from './screens/Animedetails'; 10 | import Episodewatch from './screens/Episodewatch'; 11 | import Search from './screens/Search'; 12 | import Watchlist from './screens/Watchlist'; 13 | 14 | 15 | const Stack = createNativeStackNavigator(); 16 | 17 | export default function App() { 18 | 19 | const [fontsLoaded] = useFonts({ 20 | 'pop-bold': require("./assets/fonts/Poppins-Bold.ttf"), 21 | 'pop-medium': require("./assets/fonts/Poppins-Medium.ttf"), 22 | 'pop-regular': require("./assets/fonts/Poppins-Regular.ttf"), 23 | 'lob-bold': require("./assets/fonts/LobsterTwo-Bold.ttf"), 24 | 'lob-regular': require("./assets/fonts/LobsterTwo-Regular.ttf"), 25 | }) 26 | 27 | if(!fontsLoaded){ 28 | return <AppLoading /> 29 | } 30 | 31 | 32 | return ( 33 | <GestureHandlerRootView style={styles.container}> 34 | <StatusBar style="light" /> 35 | <View style={styles.container}> 36 | <NavigationContainer> 37 | <Stack.Navigator> 38 | <Stack.Screen name='Home' component={Home} options={{ headerShown: false }} /> 39 | <Stack.Screen name='Animedetails' component={Animedetails} options={{ headerShown: false }} /> 40 | <Stack.Screen name='Episodewatch' component={Episodewatch} options={{ headerShown: false }} /> 41 | <Stack.Screen name='Search' component={Search} options={{ headerShown: false }} /> 42 | <Stack.Screen name='Watchlist' component={Watchlist} options={{ headerShown: false }} /> 43 | </Stack.Navigator> 44 | </NavigationContainer> 45 | </View> 46 | </GestureHandlerRootView> 47 | ); 48 | } 49 | 50 | const styles = StyleSheet.create({ 51 | container: { 52 | flex: 1, 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com), 2 | Copyright (c) 2011, Igino Marini. (www.ikern.com|mail@iginomarini.com), 3 | with Reserved Font Names "Lobster" and "Lobster Two". 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: 7 | http://scripts.sil.org/OFL 8 | 9 | 10 | ----------------------------------------------------------- 11 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 12 | ----------------------------------------------------------- 13 | 14 | PREAMBLE 15 | The goals of the Open Font License (OFL) are to stimulate worldwide 16 | development of collaborative font projects, to support the font creation 17 | efforts of academic and linguistic communities, and to provide a free and 18 | open framework in which fonts may be shared and improved in partnership 19 | with others. 20 | 21 | The OFL allows the licensed fonts to be used, studied, modified and 22 | redistributed freely as long as they are not sold by themselves. The 23 | fonts, including any derivative works, can be bundled, embedded, 24 | redistributed and/or sold with any software provided that any reserved 25 | names are not used by derivative works. The fonts and derivatives, 26 | however, cannot be released under any other type of license. The 27 | requirement for fonts to remain under this license does not apply 28 | to any document created using the fonts or their derivatives. 29 | 30 | DEFINITIONS 31 | "Font Software" refers to the set of files released by the Copyright 32 | Holder(s) under this license and clearly marked as such. This may 33 | include source files, build scripts and documentation. 34 | 35 | "Reserved Font Name" refers to any names specified as such after the 36 | copyright statement(s). 37 | 38 | "Original Version" refers to the collection of Font Software components as 39 | distributed by the Copyright Holder(s). 40 | 41 | "Modified Version" refers to any derivative made by adding to, deleting, 42 | or substituting -- in part or in whole -- any of the components of the 43 | Original Version, by changing formats or by porting the Font Software to a 44 | new environment. 45 | 46 | "Author" refers to any designer, engineer, programmer, technical 47 | writer or other person who contributed to the Font Software. 48 | 49 | PERMISSION & CONDITIONS 50 | Permission is hereby granted, free of charge, to any person obtaining 51 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 52 | redistribute, and sell modified and unmodified copies of the Font 53 | Software, subject to the following conditions: 54 | 55 | 1) Neither the Font Software nor any of its individual components, 56 | in Original or Modified Versions, may be sold by itself. 57 | 58 | 2) Original or Modified Versions of the Font Software may be bundled, 59 | redistributed and/or sold with any software, provided that each copy 60 | contains the above copyright notice and this license. These can be 61 | included either as stand-alone text files, human-readable headers or 62 | in the appropriate machine-readable metadata fields within text or 63 | binary files as long as those fields can be easily viewed by the user. 64 | 65 | 3) No Modified Version of the Font Software may use the Reserved Font 66 | Name(s) unless explicit written permission is granted by the corresponding 67 | Copyright Holder. This restriction only applies to the primary font name as 68 | presented to the users. 69 | 70 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 71 | Software shall not be used to promote, endorse or advertise any 72 | Modified Version, except to acknowledge the contribution(s) of the 73 | Copyright Holder(s) and the Author(s) or with their explicit written 74 | permission. 75 | 76 | 5) The Font Software, modified or unmodified, in part or in whole, 77 | must be distributed entirely under this license, and must not be 78 | distributed under any other license. The requirement for fonts to 79 | remain under this license does not apply to any document created 80 | using the Font Software. 81 | 82 | TERMINATION 83 | This license becomes null and void if any of the above conditions are 84 | not met. 85 | 86 | DISCLAIMER 87 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 89 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 90 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 91 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 92 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 93 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 94 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 95 | OTHER DEALINGS IN THE FONT SOFTWARE. 96 | -------------------------------------------------------------------------------- /screens/Search.js: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleSheet, FlatList, TextInput, ActivityIndicator, Pressable,TouchableOpacity, Image } from 'react-native' 2 | import React, {useEffect, useState} from 'react' 3 | import { recentEpisode, searchEpisode } from '../utils/data'; 4 | import themeStyle from '../config/styles'; 5 | import Ionicons from '@expo/vector-icons/Ionicons'; 6 | 7 | const Search = ({navigation,route}) => { 8 | const [searchList, setsearchList] = useState([]); 9 | const [searchText, setsearchText] = useState(''); 10 | 11 | 12 | useEffect(()=>{ 13 | let isCancelled = false; 14 | async function fetchRecent(){ 15 | const recentData = await recentEpisode(); 16 | setsearchList(recentData); 17 | } 18 | 19 | if(!isCancelled){ 20 | fetchRecent(); 21 | } 22 | 23 | return ()=>{ 24 | isCancelled = true; 25 | } 26 | }, []) 27 | 28 | useEffect(()=>{ 29 | let isCancelled = false; 30 | async function search(){ 31 | const recentData = await searchEpisode(searchText); 32 | setsearchList(recentData); 33 | } 34 | 35 | if(!isCancelled){ 36 | if(searchText != ""){ 37 | search(); 38 | } 39 | } 40 | 41 | return ()=>{ 42 | isCancelled = true; 43 | } 44 | }, [searchText]) 45 | 46 | function handleAnime(id){ 47 | navigation.navigate("Animedetails", { 48 | id:id, 49 | watchHistory: route.params.watchHistory 50 | }); 51 | } 52 | 53 | return ( 54 | <View style={{flex: 1}}> 55 | <View style={styles.mainContainer}> 56 | <View style={styles.Header}> 57 | <Pressable onPress={navigation.goBack}> 58 | <Ionicons name="arrow-back-outline" size={28} style={{padding: 10}} color={themeStyle.colors.accentColor} /> 59 | </Pressable> 60 | <TextInput placeholderTextColor="#fff" onChangeText={setsearchText} style={styles.textInput} value={searchText} autoFocus placeholder="Search..." /> 61 | </View> 62 | { 63 | (searchList.length != 0) ? 64 | ( 65 | 66 | <FlatList 67 | data={searchList} 68 | renderItem={({item})=>( 69 | <TouchableOpacity style={styles.cardBody} onPress={()=>handleAnime(item.id)}> 70 | <Image style={styles.cardImage} source={{uri: item.image}}/> 71 | </TouchableOpacity> 72 | )} 73 | keyExtractor={(item)=> item.id} 74 | numColumns={3} 75 | showsVerticalScrollIndicator={false} 76 | /> 77 | 78 | ) : 79 | ( 80 | <View style={styles.container}> 81 | <ActivityIndicator size="large" color={themeStyle.colors.accentColor} /> 82 | </View> 83 | ) 84 | 85 | } 86 | </View> 87 | </View> 88 | ) 89 | } 90 | 91 | 92 | 93 | 94 | const styles = StyleSheet.create({ 95 | contianer:{ 96 | flex: 1, 97 | justifyContent: 'center', 98 | alignItems: 'center', 99 | 100 | }, 101 | mainContainer: { 102 | flex: 1, 103 | backgroundColor: '#000' 104 | }, 105 | Header:{ 106 | marginTop: 60, 107 | borderBottomWidth: 1, 108 | paddingBottom: 18, 109 | marginBottom: 10, 110 | flexDirection: 'row', 111 | justifyContent: "space-between", 112 | position: "relative", 113 | alignItems: "center" 114 | 115 | }, 116 | title:{ 117 | fontSize: 24, 118 | fontWeight: 'bold', 119 | }, 120 | cardBody:{ 121 | marginHorizontal: 10, 122 | width: '28%', 123 | elevation: 4, 124 | marginVertical: 5 125 | }, 126 | cardImage:{ 127 | width: "100%", 128 | height: 120, 129 | borderRadius: 10, 130 | }, 131 | cardTitle:{ 132 | margin: 2, 133 | textAlign: 'center', 134 | fontSize: 14, 135 | fontWeight: "bold", 136 | padding: 5 137 | }, 138 | textInput:{ 139 | color: "#fff", 140 | padding: 10, 141 | paddingHorizontal: 15, 142 | backgroundColor: "#494F55", 143 | flex: 1, 144 | marginHorizontal: 10, 145 | borderRadius: 10 146 | } 147 | }) 148 | 149 | 150 | export default Search -------------------------------------------------------------------------------- /screens/Home.js: -------------------------------------------------------------------------------- 1 | import { View, Text, ActivityIndicator, StyleSheet, Image, FlatList, Pressable, Dimensions, ImageBackground } from 'react-native' 2 | import React, {useEffect, useRef, useState} from 'react' 3 | import { getpopularAnime, getTrendingAnime } from '../utils/data' 4 | import Ionicons from '@expo/vector-icons/Ionicons'; 5 | import Carousel from 'react-native-reanimated-carousel'; 6 | import themeStyles from "../config/styles"; 7 | import { getData } from '../utils/storage'; 8 | import WatchHistory from '../component/WatchHistory'; 9 | import Title from '../component/Title'; 10 | import Card from '../component/Card'; 11 | import IslandNav from '../navigation/islandNav'; 12 | 13 | 14 | const Home = ({navigation}) => { 15 | 16 | const [popularAnime, setPopularAnime] = useState([]); 17 | const [trendingAnime, setTrendingAnime] = useState([]); 18 | const [watchHistory, setwatchHistory] = useState([]); 19 | const [trackData, settrackData] = useState(true); 20 | const carouselRef = useRef(null); 21 | const width = Dimensions.get('window').width; 22 | 23 | useEffect(()=>{ 24 | let isCancelled = false; 25 | async function fetchAnime(){ 26 | try { 27 | let data = await getpopularAnime(); 28 | const trendingData = await getTrendingAnime(); 29 | setTrendingAnime(trendingData); 30 | setPopularAnime(data); 31 | settrackData(false); 32 | } catch (error) { 33 | console.log(error) 34 | } 35 | 36 | } 37 | if(!isCancelled){ 38 | fetchAnime(); 39 | } 40 | return ()=>{ 41 | isCancelled = true; 42 | } 43 | },[]) 44 | 45 | useEffect(()=>{ 46 | let isCancelled = false; 47 | async function fetchWatchHistory(){ 48 | const watchHistoryData = await getData('recentAnimeWatch'); 49 | if(watchHistoryData !== null){ 50 | setwatchHistory(watchHistoryData); 51 | } 52 | } 53 | if(!isCancelled){ 54 | fetchWatchHistory(); 55 | } 56 | 57 | return ()=>{ 58 | fetchWatchHistory(); 59 | } 60 | },[]) 61 | 62 | function hanldeWatchHistory(data){ 63 | setwatchHistory(data); 64 | } 65 | 66 | 67 | function handleAnime(id){ 68 | navigation.navigate("Animedetails", { 69 | id:id, 70 | watchHistory: hanldeWatchHistory 71 | }); 72 | } 73 | 74 | function handleSearch(){ 75 | navigation.navigate("Search", { 76 | watchHistory: hanldeWatchHistory 77 | }); 78 | } 79 | 80 | return ( 81 | <> 82 | { 83 | trackData ? ( 84 | <View style={styles.contianer}> 85 | <ActivityIndicator size="large" color={themeStyles.colors.accentColor} /> 86 | </View> 87 | ) : 88 | ( 89 | 90 | <View style={styles.mainContainer}> 91 | <View style={styles.Header}> 92 | <Image source={require("../assets/logo/logo.png")} style={styles.logo} /> 93 | <Text style={styles.title}>Animasi</Text> 94 | <Pressable onPress={handleSearch} style={{paddingHorizontal: 10}}> 95 | <Ionicons name="search-outline" size={28} color={themeStyles.colors.accentColor} /> 96 | </Pressable> 97 | </View> 98 | 99 | <FlatList 100 | data={trendingAnime} 101 | renderItem={({item})=>( 102 | <Card item={item} animeHandle={handleAnime} cardStyle={styles.cardBody} /> 103 | )} 104 | keyExtractor={(item)=> item.id} 105 | numColumns={3} 106 | showsVerticalScrollIndicator={false} 107 | ListHeaderComponent={ 108 | <> 109 | 110 | <Carousel 111 | loop 112 | width={width} 113 | height={width / 2} 114 | autoPlay={true} 115 | data={popularAnime} 116 | mode="horizontal-stack" 117 | modeConfig={{ 118 | snapDirection: "left", 119 | stackInterval: 18, 120 | }} 121 | pagingEnabled={true} 122 | scrollAnimationDuration={2000} 123 | renderItem={({item}) => ( 124 | <Pressable style={styles.carouselSlide} onPress={()=>handleAnime(item.id)}> 125 | {item.cover?.includes("banner") && <ImageBackground source={{uri: item.cover}} style={styles.carsoulPoster}> 126 | <Text style={styles.carsoulTitle}>{item.title.english != "" ? item.title.english : item.title.romaji}</Text> 127 | </ImageBackground>} 128 | </Pressable> 129 | )} 130 | /> 131 | 132 | {watchHistory.length > 0 && <WatchHistory data={watchHistory} animeHandle= {handleAnime} /> } 133 | 134 | <Title boldTitle="Top" NormalTitle="Airing Anime" icon="apps-outline" /> 135 | </> 136 | } 137 | /> 138 | </View> 139 | ) 140 | } 141 | {/* <IslandNav /> */} 142 | </> 143 | 144 | ) 145 | } 146 | 147 | 148 | const styles = StyleSheet.create({ 149 | contianer:{ 150 | flex: 1, 151 | justifyContent: 'center', 152 | alignItems: 'center', 153 | backgroundColor: "#000" 154 | }, 155 | mainContainer: { 156 | flex: 1, 157 | backgroundColor: "#000" 158 | 159 | }, 160 | Header:{ 161 | marginTop: 60, 162 | paddingBottom: 10, 163 | flexDirection: 'row', 164 | justifyContent: "space-between", 165 | paddingHorizontal: 10, 166 | }, 167 | logo:{ 168 | width: 40, 169 | height: 40, 170 | borderRadius: 5 171 | }, 172 | title:{ 173 | flex: 1, 174 | textAlign: "center", 175 | fontSize: 28, 176 | fontFamily: "lob-bold", 177 | color: themeStyles.colors.accentColor, 178 | marginBottom: 10 179 | }, 180 | cardBody:{ 181 | marginHorizontal: 8, 182 | width: "30%", 183 | elevation: 4, 184 | marginVertical: 5 185 | }, 186 | cardImage:{ 187 | width: "100%", 188 | height: 120, 189 | borderRadius: 10, 190 | }, 191 | cardTitle:{ 192 | margin: 2, 193 | textAlign: 'center', 194 | fontSize: 14, 195 | fontFamily: "pop-medium", 196 | padding: 5, 197 | color: "#fff" 198 | }, 199 | titleBack:{ 200 | backgroundColor: "#000", 201 | height: "100%", 202 | width: "100%", 203 | position: 'absolute', 204 | opacity: .2, 205 | bottom: 0, 206 | }, 207 | carousel: { 208 | flexGrow: 1, 209 | height: 250, 210 | marginTop: 10 211 | }, 212 | carouselSlide:{ 213 | width: "100%", 214 | maxHeight: 220, 215 | elevation: 4, 216 | borderRadius: 10, 217 | paddingHorizontal: 10 218 | }, 219 | carsoulPoster:{ 220 | height: "100%", 221 | position: 'relative', 222 | width: "100%", 223 | borderRadius: 10, 224 | overflow: 'hidden', 225 | }, 226 | carsoulTitle:{ 227 | fontSize: 14, 228 | fontFamily: "pop-medium", 229 | color: '#fff', 230 | position: 'absolute', 231 | bottom: 10, 232 | marginHorizontal: 10 233 | }, 234 | 235 | }) 236 | 237 | export default Home -------------------------------------------------------------------------------- /screens/Episodewatch.js: -------------------------------------------------------------------------------- 1 | import { View, Text, Switch,ActivityIndicator,StyleSheet, FlatList, TouchableOpacity } from 'react-native' 2 | import React, { useEffect, useRef, useState } from 'react' 3 | import ReadMore from 'react-native-read-more-text'; 4 | import { Video } from 'expo-av'; 5 | import * as ScreenOrientation from 'expo-screen-orientation'; 6 | import { getAnimeVideoLink } from '../utils/data'; 7 | import {Dimensions} from 'react-native'; 8 | import themeStyles from "../config/styles"; 9 | import Ionicons from '@expo/vector-icons/Ionicons'; 10 | 11 | 12 | const cleanHTML= (html) => { 13 | return html.replace(/<[^>]*>?/gm, ""); 14 | }; 15 | 16 | 17 | const Episodewatch = ({route}) => { 18 | const [videoUrl, setVideoUrl] = useState(''); 19 | const [Referer, setReferer] = useState(''); 20 | const [animeDetails, setAnimeDetails] = useState([]); 21 | const [backupUrl, setbackupUrl] = useState([]); 22 | const [episodeLists, setepisodeList] = useState([]); 23 | const [videoId, setVideoId] = useState(route.params.id); 24 | const [videoStatus, setvideoStatus] = useState(true); 25 | const videoRef = useRef(null); 26 | 27 | function setOrientation() { 28 | if (Dimensions.get('window').height > Dimensions.get('window').width) { 29 | //Device is in portrait mode, rotate to landscape mode. 30 | ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); 31 | } 32 | else { 33 | //Device is in landscape mode, rotate to portrait mode. 34 | ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT); 35 | } 36 | } 37 | 38 | useEffect(()=>{ 39 | let isCancelled = false; 40 | async function getVideoUrl(){ 41 | const VideoData = await getAnimeVideoLink(videoId); 42 | setVideoUrl(VideoData.sources[3].url); 43 | setbackupUrl(VideoData.sources[VideoData.sources.length - 1].url); 44 | setReferer(VideoData.headers.Referer); 45 | setAnimeDetails(route.params.animeDetails) 46 | setepisodeList(route.params.episodeLists) 47 | setvideoStatus(false) 48 | } 49 | if(!isCancelled){ 50 | getVideoUrl(); 51 | } 52 | return ()=>{ 53 | isCancelled = true 54 | } 55 | }, [videoId, videoStatus]) 56 | 57 | 58 | 59 | 60 | async function handleEpisode(id) 61 | { 62 | setVideoId(id) 63 | setvideoStatus(true) 64 | } 65 | 66 | const _renderTruncatedFooter = (handlePress) => { 67 | return ( 68 | <Text style={{color: themeStyles.colors.accentColor, marginTop: 5, marginBottom: 10}} onPress={handlePress}> 69 | Read more 70 | </Text> 71 | ); 72 | } 73 | 74 | const _renderRevealedFooter = (handlePress) => { 75 | return ( 76 | <Text style={{color:themeStyles.colors.accentColor, marginTop: 5, marginBottom: 10}} onPress={handlePress}> 77 | Show less 78 | </Text> 79 | ); 80 | } 81 | 82 | const handleEpisodeurl = () => { 83 | setVideoUrl(backupUrl) 84 | } 85 | 86 | 87 | 88 | return ( 89 | <View style={{flex: 1}}> 90 | { videoStatus ? ( 91 | <View style={styles.contianer}> 92 | <ActivityIndicator size="large" color={themeStyles.colors.accentColor} /> 93 | </View> 94 | ) : 95 | ( 96 | <> 97 | <Video 98 | source={{ uri: videoUrl, headers: {"Referer": Referer,"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0"} }} 99 | ref={videoRef} 100 | rate={1.0} 101 | volume={1.0} 102 | isMuted={false} 103 | resizeMode="contain" 104 | shouldPlay 105 | onFullscreenUpdate={setOrientation} 106 | style={{ width: "100%", height: 238 }} 107 | posterSource={{uri: "https://media.tenor.com/Qp72t2HFhA0AAAAC/tako-tentacult.gif"}} 108 | usePoster={true} 109 | posterStyle={{ 110 | resizeMode: "cover" 111 | }} 112 | useNativeControls 113 | onError={(e)=>{ 114 | console.log(e) 115 | handleEpisodeurl(); 116 | }} 117 | /> 118 | <View style={{paddingHorizontal: 10, flex: 1, backgroundColor: "#000"}}> 119 | 120 | <View style={{flex: 1, height: 200}}> 121 | <FlatList 122 | initialNumToRender={10} 123 | style={{flex: 1}} 124 | data={episodeLists} 125 | renderItem={({item})=>( 126 | <TouchableOpacity style={styles.episodeTitle} onPress={()=> handleEpisode(item.id)}> 127 | <Text style={{fontSize: 22, fontFamily: "lob-bold", color: "#fff"}}>{item.number}</Text> 128 | </TouchableOpacity> 129 | )} 130 | keyExtractor={(item)=>item.number} 131 | numColumns={4} 132 | showsVerticalScrollIndicator={false} 133 | ListHeaderComponent={ 134 | <> 135 | <View style={{paddingTop: 20}}> 136 | <Text style={{fontFamily: 'pop-bold', color: "#fff",paddingBottom: 5, fontSize: 22, color: themeStyles.colors.accentColor}}>{animeDetails.title.english}</Text> 137 | <Text style={{fontFamily: 'pop-bold', color: "#fff",paddingBottom: 5}}>Description</Text> 138 | <ReadMore 139 | numberOfLines={5} 140 | renderTruncatedFooter={_renderTruncatedFooter} 141 | renderRevealedFooter={_renderRevealedFooter} 142 | > 143 | <Text style={styles.description}>{cleanHTML(animeDetails.description)}</Text> 144 | </ReadMore> 145 | <Text style={{fontFamily: 'pop-bold', color: "#fff", paddingBottom: 10}}>Geners</Text> 146 | <View style={{fontFamily: 'pop-regular', color: "#fff", flexDirection: "row", flexWrap: 'wrap'}}>{(animeDetails.genres?.map((item)=>( 147 | <View key={`${item}1`} style={styles.genersContainer}> 148 | <Text key={item} style={styles.geners}>{item}</Text> 149 | </View> 150 | )))}</View> 151 | <View style={{flexDirection: 'row', flexWrap: "wrap", justifyContent: 'center',alignItems: 'center'}}> 152 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Sub Or Dub:</Text> 153 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.subOrDub.toUpperCase()}</Text> 154 | 155 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Status:</Text> 156 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.status}</Text> 157 | 158 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Rating:</Text> 159 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.rating}</Text> 160 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Release Date:</Text> 161 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.releaseDate}</Text> 162 | 163 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Anime type:</Text> 164 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.type}</Text> 165 | </View> 166 | </View> 167 | <View style={{flexDirection: "row", alignItems: "center", marginVertical: 10}}> 168 | <Ionicons name="list-outline" size={25} color={themeStyles.colors.accentColor} /> 169 | <Text style={{ 170 | fontFamily: "pop-bold",color: "#fff", alignItems: "center",paddingHorizontal: 5, fontSize: 18 171 | }}>Episode List</Text> 172 | </View> 173 | </> 174 | } 175 | /> 176 | </View> 177 | </View> 178 | </> 179 | ) 180 | } 181 | </View> 182 | ) 183 | } 184 | 185 | 186 | const styles = StyleSheet.create({ 187 | contianer:{ 188 | flex: 1, 189 | justifyContent: 'center', 190 | alignItems: 'center', 191 | backgroundColor: "#000" 192 | }, 193 | description:{ 194 | fontFamily: "pop-regular", 195 | color: '#fff', 196 | marginBottom: 10 197 | 198 | }, 199 | genersContainer:{ 200 | justifyContent: "center", 201 | alignItems: 'center', 202 | height: 30, 203 | paddingHorizontal: 8, 204 | borderRadius: 5, 205 | backgroundColor: themeStyles.colors.accentColor, 206 | marginHorizontal: 5, 207 | marginBottom: 8, 208 | }, 209 | geners:{ 210 | color: '#000', 211 | fontFamily: 'pop-medium', 212 | }, 213 | AnimeTitle: { 214 | color: "#fff", 215 | fontSize: 20, 216 | position: "absolute", 217 | bottom: 20, 218 | marginHorizontal: 10, 219 | width: "60%", 220 | fontFamily: "pop-medium" 221 | }, 222 | episodeTitle:{ 223 | borderWidth: 2, 224 | borderColor: "#fff", 225 | padding: 3, 226 | margin: 5, 227 | width: "22.5%", 228 | height: 50, 229 | justifyContent: "center", 230 | alignItems: "center", 231 | borderRadius: 15, 232 | }, 233 | }) 234 | 235 | export default Episodewatch -------------------------------------------------------------------------------- /screens/Animedetails.js: -------------------------------------------------------------------------------- 1 | import { View, Text, Switch, ImageBackground, ActivityIndicator, Image, StyleSheet, FlatList, Pressable, TouchableOpacity } from 'react-native' 2 | import React, { useEffect, useState } from 'react' 3 | import ReadMore from 'react-native-read-more-text'; 4 | import { getAnimeInfo } from '../utils/data'; 5 | import themeStyles from "../config/styles"; 6 | import { LinearGradient } from "expo-linear-gradient"; 7 | import Ionicons from '@expo/vector-icons/Ionicons'; 8 | import { storeData, getData } from '../utils/storage'; 9 | 10 | const cleanHTML= (html) => { 11 | return html.replace(/<[^>]*>?/gm, ""); 12 | }; 13 | 14 | const Animedetails = ({route, navigation}) => { 15 | 16 | const [animeDetails, setAnimeDetails] = useState([]); 17 | const [checkData, setcheckData] = useState(true); 18 | const [subordub, setSubOrDub] = useState(true); 19 | const [episodeLists, setepisodeList] = useState([]); 20 | const [recentAnimeList, setrecentAnimeList] = useState([]); 21 | 22 | useEffect(()=>{ 23 | let isCancelled = false; 24 | const id = route.params.id; 25 | async function getAnimeDetails (){ 26 | const animeData = await getAnimeInfo(id, subordub) 27 | setAnimeDetails(animeData); 28 | setepisodeList(animeData.episodes.reverse()) 29 | setcheckData(false); 30 | } 31 | 32 | if(!isCancelled){ 33 | getAnimeDetails(); 34 | } 35 | 36 | return ()=>{ 37 | isCancelled = true 38 | } 39 | },[subordub]) 40 | 41 | const handleDubOrSub = ()=>{ 42 | setcheckData(true); 43 | setSubOrDub(!subordub); 44 | } 45 | 46 | useEffect(()=>{ 47 | let isCancelled = false; 48 | async function getRecentAnimeList(){ 49 | const recentAnimeData = await getData('recentAnimeWatch'); 50 | if(recentAnimeData !== null){ 51 | setrecentAnimeList(recentAnimeData); 52 | } 53 | } 54 | if(!isCancelled){ 55 | getRecentAnimeList(); 56 | } 57 | return ()=>{ 58 | isCancelled = true 59 | } 60 | }, []) 61 | 62 | async function handleEpisode(id){ 63 | try { 64 | const data = [animeDetails,...recentAnimeList.filter(item => item.id !== animeDetails.id)]; 65 | setrecentAnimeList(data) 66 | route.params.watchHistory(data) 67 | await storeData(data, 'recentAnimeWatch') 68 | } catch (error) { 69 | console.log(error) 70 | } 71 | 72 | navigation.navigate("Episodewatch", { 73 | id, 74 | animeDetails: animeDetails, 75 | episodeLists 76 | }) 77 | } 78 | 79 | const _renderTruncatedFooter = (handlePress) => { 80 | return ( 81 | <Text style={{color: themeStyles.colors.accentColor, marginTop: 5, marginBottom: 10}} onPress={handlePress}> 82 | Read more 83 | </Text> 84 | ); 85 | } 86 | 87 | const _renderRevealedFooter = (handlePress) => { 88 | return ( 89 | <Text style={{color:themeStyles.colors.accentColor, marginTop: 5, marginBottom: 10}} onPress={handlePress}> 90 | Show less 91 | </Text> 92 | ); 93 | } 94 | 95 | const _handleTextReady = () => { 96 | // ... 97 | } 98 | 99 | 100 | return ( 101 | <View style={{flex: 1}}> 102 | { 103 | checkData ? ( 104 | <View style={styles.container}> 105 | <ActivityIndicator size="large" color={themeStyles.colors.accentColor} /> 106 | </View> 107 | ) : 108 | (<View style={styles.mainContent}> 109 | <View style={styles.firsthalf}> 110 | <ImageBackground style={styles.bannerImage} source={{uri: animeDetails?.cover != "" ? animeDetails.cover : animeDetails.image }}> 111 | <LinearGradient colors={["#bfafb2","#000"]} style={styles.overlay}></LinearGradient> 112 | <Pressable onPress={navigation.goBack} style={{position: 'absolute', top: "18%", left: "5%"}}> 113 | <Ionicons name="arrow-back-outline" size={25} color="#fff" /> 114 | </Pressable> 115 | <Text style={styles.AnimeTitle}>{animeDetails.title.english}</Text> 116 | <Image source={{uri: animeDetails.image}} style={styles.CoverImage} /> 117 | </ImageBackground> 118 | </View> 119 | 120 | 121 | 122 | <View style={{paddingHorizontal: 10, flex: 1}}> 123 | 124 | <View style={{flex: 1, height: 200}}> 125 | <FlatList 126 | initialNumToRender={10} 127 | maxToRenderPerBatch={10} 128 | style={{flex: 1}} 129 | data={episodeLists} 130 | renderItem={({item})=>( 131 | <TouchableOpacity style={styles.episodeTitle} onPress={()=> handleEpisode(item.id)}> 132 | <Text style={{fontSize: 22, fontFamily: "lob-bold", color: "#fff"}}>{item.number}</Text> 133 | </TouchableOpacity> 134 | )} 135 | keyExtractor={(item)=>item.number} 136 | numColumns={4} 137 | showsVerticalScrollIndicator={false} 138 | ListHeaderComponent={ 139 | 140 | <> 141 | <View style={{paddingTop: 20}}> 142 | <Text style={{fontFamily: 'pop-bold', color: "#fff",paddingBottom: 5}}>Description</Text> 143 | <ReadMore 144 | numberOfLines={5} 145 | renderTruncatedFooter={_renderTruncatedFooter} 146 | renderRevealedFooter={_renderRevealedFooter} 147 | onReady={_handleTextReady}> 148 | <Text style={styles.description}>{cleanHTML(animeDetails.description)}</Text> 149 | </ReadMore> 150 | <Text style={{fontFamily: 'pop-bold', color: "#fff", paddingBottom: 10}}>Characters</Text> 151 | 152 | <FlatList 153 | style={{flex: 1}} 154 | data={animeDetails.characters} 155 | keyExtractor={(item)=> item.id} 156 | renderItem={(item)=> ( 157 | <TouchableOpacity style={[styles.watchcardBody]}> 158 | <Image style={styles.cardImage} source={{uri: item.item.image}}/> 159 | <Text style={[styles.cardTitle, {marginTop: 5}]}> {item.item.name.userPreferred}</Text> 160 | <Text style={[styles.cardTitle, {fontSize: 12}]}>({item.item.role})</Text> 161 | </TouchableOpacity> 162 | )} 163 | horizontal={true} 164 | 165 | /> 166 | 167 | 168 | 169 | <Text style={{fontFamily: 'pop-bold', color: "#fff", paddingBottom: 10}}>Geners</Text> 170 | <View style={{fontFamily: 'pop-regular', color: "#fff", flexDirection: "row", flexWrap: 'wrap'}}>{(animeDetails.genres?.map((item)=>( 171 | <View key={`${item}1`} style={styles.genersContainer}> 172 | <Text key={item} style={styles.geners}>{item}</Text> 173 | </View> 174 | )))}</View> 175 | <View style={{flexDirection: 'row', flexWrap: "wrap", justifyContent: 'center',alignItems: 'center'}}> 176 | <View style={{flexDirection: "row", alignItems: "center"}}> 177 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>Sub</Text> 178 | <Switch 179 | trackColor={{ false: "#808080", true: "#808080" }} 180 | thumbColor={subordub ? themeStyles.colors.accentColor : "#f4f3f4"} 181 | ios_backgroundColor="#3e3e3e" 182 | onValueChange={handleDubOrSub} 183 | value={subordub} 184 | /> 185 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>Dub</Text> 186 | </View> 187 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Status:</Text> 188 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.status}</Text> 189 | 190 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Rating:</Text> 191 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.rating}</Text> 192 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Release Date:</Text> 193 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.releaseDate}</Text> 194 | 195 | <Text style={{fontFamily: 'pop-bold', color: "#fff"}}>Anime type:</Text> 196 | <Text style={{fontFamily: 'pop-regular', color: themeStyles.colors.accentColor, paddingHorizontal: 10}}>{animeDetails.type}</Text> 197 | </View> 198 | </View> 199 | <View style={{flexDirection: "row", alignItems: "center", marginVertical: 10}}> 200 | <Ionicons name="list-outline" size={25} color={themeStyles.colors.accentColor} /> 201 | <Text style={{ 202 | fontFamily: "pop-bold",color: "#fff", alignItems: "center",paddingHorizontal: 5, fontSize: 18 203 | }}>Episode List</Text> 204 | </View> 205 | </> 206 | } 207 | /> 208 | </View> 209 | </View> 210 | </View>) 211 | } 212 | </View> 213 | ) 214 | } 215 | 216 | 217 | const styles = StyleSheet.create({ 218 | container: { 219 | flex: 1, 220 | justifyContent: 'center', 221 | alignItems: "center", 222 | backgroundColor: "#000" 223 | }, 224 | mainContent:{ 225 | flex: 1, 226 | backgroundColor: "#000" 227 | }, 228 | firsthalf:{ 229 | height: "26%", 230 | position: "relative", 231 | }, 232 | bannerImage:{ 233 | height: "100%" 234 | }, 235 | overlay:{ 236 | height: "100%", 237 | width: "100%", 238 | opacity: .7, 239 | position: 'absolute', 240 | }, 241 | watchcardBody:{ 242 | marginHorizontal: 8, 243 | elevation: 4, 244 | width: 120 245 | }, 246 | cardImage:{ 247 | width: "100%", 248 | height: 120, 249 | minWidth: 120, 250 | borderRadius: 10, 251 | }, 252 | cardTitle:{ 253 | textAlign: 'center', 254 | fontSize: 14, 255 | fontFamily: "pop-medium", 256 | color: "#fff", 257 | }, 258 | description:{ 259 | fontFamily: "pop-regular", 260 | color: '#fff', 261 | marginBottom: 10 262 | 263 | }, 264 | genersContainer:{ 265 | justifyContent: "center", 266 | alignItems: 'center', 267 | height: 30, 268 | paddingHorizontal: 8, 269 | borderRadius: 5, 270 | backgroundColor: themeStyles.colors.accentColor, 271 | marginHorizontal: 5, 272 | marginBottom: 8, 273 | }, 274 | geners:{ 275 | color: '#000', 276 | fontFamily: 'pop-medium', 277 | }, 278 | AnimeTitle: { 279 | color: "#fff", 280 | fontSize: 20, 281 | position: "absolute", 282 | bottom: 20, 283 | marginHorizontal: 10, 284 | width: "60%", 285 | fontFamily: "pop-medium" 286 | }, 287 | episodeTitle:{ 288 | borderWidth: 2, 289 | borderColor: "#fff", 290 | padding: 3, 291 | margin: 5, 292 | width: "22.5%", 293 | height: 50, 294 | justifyContent: "center", 295 | alignItems: "center", 296 | borderRadius: 15, 297 | }, 298 | CoverImage:{ 299 | height: 180, 300 | width: 130, 301 | elevation: 10, 302 | position: 'absolute', 303 | right: "5%", 304 | bottom: "-10%", 305 | zIndex: 9, 306 | borderRadius: 10 307 | } 308 | }) 309 | export default Animedetails --------------------------------------------------------------------------------