Adding fetchKanjis thunk and startup component

master
Arthur VALIN 2 years ago
parent f1a4952a2a
commit 0cccc65a3e

Binary file not shown.

@ -1,13 +1,15 @@
{
"ExpandedNodes": [
"",
"\\assets",
"\\components",
"\\model",
"\\navigation",
"\\pages",
"\\redux"
"\\redux",
"\\redux\\actions",
"\\redux\\reducers",
"\\redux\\thunks"
],
"SelectedNode": "\\components\\KanjiListCell.tsx",
"SelectedNode": "\\components\\KanjiListSearchPanel.tsx",
"PreviewInSolutionExplorer": false
}

Binary file not shown.

@ -1,26 +1,25 @@
import React from 'react';
import React, { useEffect } from 'react';
import store from "./redux/store";
import { Provider } from 'react-redux';
import { Provider, useDispatch } from 'react-redux';
import { StatusBar } from 'expo-status-bar';
import { Keyboard, SafeAreaView, StyleSheet, TouchableWithoutFeedback} from 'react-native';
import Header from './components/Header';
import TabBar from './navigation/TabBar';
import { InitStack } from './navigation/Startup';
export default function App() {
return (
<Provider store={store}>
<TouchableWithoutFeedback onPress={() => { Keyboard.dismiss(); }}>
<SafeAreaView style={styles.container}>
<Header />
<TabBar/>
<StatusBar style="auto" />
</SafeAreaView>
</TouchableWithoutFeedback >
<TouchableWithoutFeedback onPress={() => { Keyboard.dismiss(); }}>
<SafeAreaView style={styles.container}>
<InitStack/>
<StatusBar style="auto" />
</SafeAreaView>
</TouchableWithoutFeedback>
</Provider>
);

@ -23,7 +23,7 @@ export const animatedStyles = {
{
translateY: animation.interpolate({
inputRange: [0, 1],
outputRange: [0, -Dimensions.get('window').height / 2]
outputRange: [0, -Dimensions.get('window').height/2]
}),
},
],

@ -29,7 +29,8 @@ const DetailExamples = (props: detailExamplesProps) => {
const detailExamplesStyle_light = StyleSheet.create({
container: {
width: '100%',
paddingBottom: 50,
paddingRight: 20,
paddingLeft: 20,
},
cellContainer: {
flex: 1,
@ -47,7 +48,8 @@ const detailExamplesStyle_light = StyleSheet.create({
const detailExamplesStyle_dark = StyleSheet.create({
container: {
width: '100%',
paddingBottom: 50,
paddingRight: 20,
paddingLeft: 20,
},
cellContainer: {
flex: 1,

@ -1,11 +1,13 @@
import React, {useEffect, useRef, useState } from 'react';
import { SketchCanvas, SketchCanvasRef } from 'rn-perfect-sketch-canvas';
import { StyleSheet, Button, View, Text, useColorScheme } from 'react-native';
import { StyleSheet, Button, View, Text, useColorScheme, Touchable } from 'react-native';
import { SvgXml } from 'react-native-svg';
import Slider from '@react-native-community/slider'
import { useSelector } from 'react-redux';
import { Kanji, KanjiMapper } from '../model/kanji';
import { Kanji } from '../model/kanji';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { KanjiMapper } from '../model/kanjiMapper';
type DrawingCanvaProps = {
backgroundImage: string;
@ -13,16 +15,15 @@ type DrawingCanvaProps = {
const DrawingCanva = (props: DrawingCanvaProps) => {
const style = useColorScheme() == 'light' ? style_light : style_dark;
const style = useColorScheme() == 'light' ? style_light : style_dark;
const canvasRef = useRef<SketchCanvasRef>(null);
const [strokeWidth, setStroke] = useState(5);
const [isCanvasReady, setIsCanvasReady] = useState(false);
const [imgXml, setImgXml] = useState('<svg></svg>');
const [drawnStrokes, setDrawnStrokes] = useState(0);
const selectedKanji = KanjiMapper.SerializedObjectToKanji(useSelector(state => state.kanjiReducer.selectedKanji));
useEffect(() => {
fetchXml();
if (canvasRef.current) {
@ -30,6 +31,10 @@ const DrawingCanva = (props: DrawingCanvaProps) => {
}
}, [canvasRef.current, selectedKanji]);
const getCanvasStrokeCount = () => {
return canvasRef.current?.toPoints().length;
}
const fetchXml = async () => {
if (selectedKanji instanceof Kanji) {
const xml = await (await fetch(selectedKanji.image)).text();
@ -37,7 +42,6 @@ const DrawingCanva = (props: DrawingCanvaProps) => {
}
}
return (
<View style={style.container}>
{selectedKanji && (
@ -49,17 +53,18 @@ const DrawingCanva = (props: DrawingCanvaProps) => {
opacity={0.1}
style={style.back}
/>)}
<SketchCanvas
ref={canvasRef}
strokeColor={style.canvas.strokeColor}
strokeWidth={strokeWidth}
containerStyle={style.canvas}
/>
<SketchCanvas
ref={canvasRef}
strokeColor={style.canvas.strokeColor}
strokeWidth={strokeWidth}
containerStyle={style.canvas}
/>
<Slider
style={style.slider}
onValueChange={(val) => setStroke(val)}
minimumValue={5}
maximumValue={10}
maximumValue={15}
minimumTrackTintColor={"#FF5C5C"}
/>
{isCanvasReady && (<View style={style.menu}>

@ -0,0 +1,78 @@
import { useNavigation } from '@react-navigation/native';
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, TouchableOpacity, useColorScheme, Animated } from 'react-native';
import { Check } from "react-native-feather";
interface gradeChipProps {
grade: number;
onSelect: (item: string, isSelected: Boolean) => void;
}
const GradeChip = (props: gradeChipProps) => {
const [chipStyle, setChipStyle] = useState(style);
const [isSelected, setIsSelected] = useState(false);
useEffect(() => {
setChipStyle(isSelected ? styleSELECTED : style);
}, [isSelected]);
const select = () => {
props.onSelect("Grade "+props.grade, isSelected);
setIsSelected(!isSelected);
}
return (
<TouchableOpacity style={chipStyle.chip} onPress={() => { select() }}>
<Text style={chipStyle.text}>Grade {props.grade}</Text>
{isSelected && (<Check style={chipStyle.icon} />)}
</TouchableOpacity>
);
};
const style = StyleSheet.create({
chip: {
backgroundColor: "#FF5C5C",
borderRadius: 10,
margin: 5,
justifyContent: "center",
},
icon: {
color: "white",
margin: 5
},
text: {
color: "white",
fontWeight: "bold",
fontSize: "18em",
padding: 5
},
})
const styleSELECTED = StyleSheet.create({
chip: {
backgroundColor: "#FF5C5C",
borderRadius: 10,
margin: 5,
justifyContent: "center",
flexDirection: "row",
alignItems: "center",
},
icon: {
color: "white",
margin: 5
},
text: {
color: "white",
fontWeight: "bold",
fontSize: "18em",
padding: 5
},
})
export default GradeChip;

@ -1,6 +1,6 @@
import React from 'react';
import { Animated, StyleSheet, TextInput } from 'react-native';
import { startAnimation, stopAnimation, animatedStyles } from '../assets/answerAnimation'
import { startAnimation, stopAnimation, animatedStyles } from '../assets/animations/answerAnimation'
const KanjiAnswerField = () => {

@ -1,6 +1,10 @@
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Button, useColorScheme } from 'react-native';
import { SvgUri, SvgXml } from 'react-native-svg';
import { useSelector } from 'react-redux';
import { Kanji } from '../model/kanji';
import { KanjiListByGrade } from '../model/kanjiListByGrades';
import { KanjiMapper } from '../model/kanjiMapper';
import KanjiAnswerField from './KanjiAnswerField';
type KanjiProps = {
@ -12,6 +16,7 @@ const KanjiCard = (props: KanjiProps) => {
const kanjiCardStyle = useColorScheme() == 'light' ? kanjiCardStyle_light : kanjiCardStyle_dark;
const options = {
method: 'GET',
headers: {
@ -21,32 +26,50 @@ const KanjiCard = (props: KanjiProps) => {
}
const [loading, setLoading] = useState(true);
const [res, setData] = useState(null);
const [kanji, setKanji] = useState((): Kanji | null => { return null });
const [imgXml, setImgXml] = useState('<svg></svg>');
var kanjis: KanjiListByGrade = useSelector(state => state.kanjiReducer.kanjis);
const allKanjis: Kanji[] = [].concat(...Object.values(kanjis))
const selectedKanji = allKanjis[Math.floor(Math.random() * allKanjis.length)]
const fetchData = async () => {
await fetch(`https://kanjialive-api.p.rapidapi.com/api/public/kanji/${props.kanji}`, options)
.then(async response => {
const data = await response.json()
setData(data);
const xml = await (await fetch(data.kanji.video.poster)).text();
setImgXml(xml);
setKanji(KanjiMapper.ApiJsonToKanji(data));
})
.catch(err => console.log(err));
}
const fetchXml = async () => {
if (kanji) {
const xml = await (await fetch(kanji?.image!)).text();
setImgXml(xml);
}
}
useEffect(() => {
setLoading(true);
fetchData().then(_ => {
setKanji(selectedKanji);
setLoading(false);
});
}, []);
useEffect(() => {
setLoading(true);
fetchXml().then(_ => {
setLoading(false);
});
}, [kanji]);
return (
<View style={kanjiCardStyle.container}>
<Text style={kanjiCardStyle.text}> {loading ? <Text>Loading...</Text> : <Text>{res.kanji.onyomi.katakana}</Text>}</Text>
<Text style={kanjiCardStyle.text}> {loading ? <Text>Loading...</Text> : <Text>{kanji?.onyomi}</Text>}</Text>
{!loading && (
<SvgXml
xml={imgXml
@ -55,7 +78,7 @@ const KanjiCard = (props: KanjiProps) => {
height="200"
/>
)}
<Text style={kanjiCardStyle.text}> {loading ? <Text /> : <Text>{res.kanji.meaning.english}</Text>}</Text>
<Text style={kanjiCardStyle.text}> {loading ? <Text /> : <Text>{kanji?.meaning}</Text>}</Text>
<KanjiAnswerField />
<Button title="OK" color="#FF5C5C" />
</View>

@ -1,52 +1,67 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Text, SectionList, View, StyleSheet, useColorScheme } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { Kanji } from '../model/kanji';
import { KanjiListByGrade } from '../model/kanjiListByGrades';
import KanjiListCell from './KanjiListCell';
import KanjiListSearchPanel from './KanjiListSearchPanel';
const KanjiList = () => {
const kanjiListStyle = useColorScheme() == 'light' ? kanjiListStyle_light : kanjiListStyle_dark;
const dispatch = useDispatch();
const [res, setData] = useState(null);
const [loading, setLoading] = useState(true);
var kanjis: KanjiListByGrade = useSelector(state => state.kanjiReducer.kanjis);
const [selectedItems, setSelectedItems] = useState<{title: string, data: Kanji[]}[]>([]);
const updateSelectedItems = (item: string, isSelected: Boolean) => {
if (!isSelected) {
setSelectedItems([...selectedItems, {
title: item,
data: kanjis[item]
}]);
} else {
setSelectedItems(selectedItems.filter((selectedItem) => selectedItem.title !== item));
}
};
useEffect(() => {
}, [dispatch]);
return (
<View style={kanjiListStyle.container}>
<SectionList sections={[
{
title: "N5",
data: [
new Kanji("親", "parent", "https://media.kanjialive.com/kanji_strokes/shita(shii)_16.svg"),
new Kanji("雨", "rain", "https://media.kanjialive.com/kanji_strokes/u-ame_8.svg"),
new Kanji("貴", "noble", "https://media.kanjialive.com/kanji_strokes/ki-touto(i)_12.svg")
]
},
{
title: "N4",
data: [
new Kanji("親", "parent", "https://media.kanjialive.com/kanji_strokes/shita(shii)_16.svg"),
new Kanji("雨", "rain", "https://media.kanjialive.com/kanji_strokes/u-ame_8.svg"),
new Kanji("貴", "noble", "https://media.kanjialive.com/kanji_strokes/ki-touto(i)_12.svg")
]
}
]}
<View>
<KanjiListSearchPanel onSelect={updateSelectedItems} />
<SectionList
sections={selectedItems}
renderItem={
({ item }) => <KanjiListCell kanji={item} />
}
renderSectionHeader={({ section }) => (
<Text style={kanjiListStyle.sectionHeader}>{section.title}</Text>
)}
keyExtractor={item => `basicListEntry-${item.character}`}
keyExtractor={item => `basicListEntry-${item.character}`
}
style={kanjiListStyle.list}
>
</SectionList>
</View>
);
};
};
const kanjiListStyle_light = StyleSheet.create({
container: {
width: '100%',
height: '100%',
padding: 10
},
sectionHeader: {
paddingTop: 2,
paddingLeft: 10,
@ -62,9 +77,24 @@ const kanjiListStyle_light = StyleSheet.create({
fontSize: 18,
height: 44,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
width: 200,
backgroundColor: "white",
borderRadius: 20,
borderColor: "black"
},
})
const kanjiListStyle_dark = StyleSheet.create({
list: {
},
chipList: {
height: 10
},
container: {
width: '100%',
height: '100%',
@ -85,6 +115,15 @@ const kanjiListStyle_dark = StyleSheet.create({
fontSize: 18,
height: 44,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
backgroundColor: "white",
borderRadius: 20,
borderColor: "black"
},
})

@ -33,12 +33,13 @@ const cellStyle_light = StyleSheet.create({
},
text: {
color: "black",
width: "90%"
},
kanji: {
fontWeight: "bold",
color: "black",
fontSize: "20em",
width: "50%"
width: "10%"
}
})
@ -54,12 +55,13 @@ const cellStyle_dark = StyleSheet.create({
},
text: {
color: "white",
width: "90%"
},
kanji: {
fontWeight: "bold",
color: "white",
fontSize: "20em",
width: "50%"
width: "10%"
},
})

@ -0,0 +1,50 @@
import { useNavigation } from "@react-navigation/native";
import React, { useState } from "react";
import { FlatList, TextInput, StyleSheet, View } from "react-native";
import GradeChip from './GradeChip';
interface kanjiListSeachPanelProps {
onSelect: (item: string, isSelected: Boolean) => void;
}
const KanjiListSearchPanel = (props: kanjiListSeachPanelProps) => {
const navigator = useNavigation();
return (
<View style={panelStyle.container}>
<TextInput style={panelStyle.input} />
<FlatList
data={[1, 2, 3, 4, 5, 6]}
renderItem={(item) => <GradeChip grade={item.item} onSelect={props.onSelect} />}
horizontal={true}
showsHorizontalScrollIndicator={false}
>
</FlatList>
</View>
);
};
const panelStyle = StyleSheet.create({
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
width: 200,
backgroundColor: "white",
borderRadius: 20,
borderColor: "black",
width: "80%"
},
container: {
justifyContent: "center",
alignItems: "center",
height: "20%"
}
})
export default KanjiListSearchPanel;

@ -27,7 +27,7 @@ const KanjiPlaygroundList = (props: kanjiPlaygroundListProps) => {
data={props.data}
renderItem={
({ item }) => (
<TouchableOpacity onPress={() => { dispatch(setSelectedKanji(item)); console.log(item) }} style={kanjiPlaygroundList.entry}>
<TouchableOpacity onPress={() => { dispatch(setSelectedKanji(item))}} style={kanjiPlaygroundList.entry}>
<Text style={kanjiPlaygroundList.entryText}>{item.character}</Text>
</TouchableOpacity>
)

@ -84,36 +84,4 @@ export class Kanji {
}
export class KanjiMapper {
static ApiJsonToKanji(json: any): Kanji {
var radical: { character: string, position: string } = {
character: json.radical.character,
position: json.radical.position.icon
};
var examples: { japanese: string, english: string }[] = [];
json.examples.forEach(
(entry) => {
examples.push({
japanese: entry.japanese,
english: entry.meaning.english
})
}
)
return new Kanji(json.kanji.character, json.kanji.meaning.english, json.kanji.video.poster,
json.kanji.video.mp4, json.kanji.strokes.count, json.kanji.onyomi.katakana, json.kanji.kunyomi.hiragana,
radical, examples);
}
// @ts-ignore
static SerializedObjectToKanji(obj): Kanji | null {
if (!obj) return null;
return new Kanji(obj.character, obj.meaning, obj.image, obj.animation, obj.strokes, obj.onyomi, obj.kunyomi, obj.radical, obj.examples);
}
}

@ -0,0 +1,21 @@
import { Kanji } from "./kanji"
export type KanjiListByGrade = {
"Grade 1": Kanji[],
"Grade 2": Kanji[],
"Grade 3": Kanji[],
"Grade 4": Kanji[],
"Grade 5": Kanji[],
"Grade 6": Kanji[],
}[]
export const initKanjiListByGrade = (): KanjiListByGrade => {
return {
"Grade 1": [],
"Grade 2": [],
"Grade 3": [],
"Grade 4": [],
"Grade 5": [],
"Grade 6": [],
}
}

@ -0,0 +1,33 @@
import { Kanji } from "./kanji";
export class KanjiMapper {
static ApiJsonToKanji(json: any): Kanji {
var radical: { character: string, position: string } = {
character: json.radical.character,
position: json.radical.position.icon
};
var examples: { japanese: string, english: string }[] = [];
json.examples.forEach(
(entry) => {
examples.push({
japanese: entry.japanese,
english: entry.meaning.english
})
}
)
return new Kanji(json.kanji.character, json.kanji.meaning.english, json.kanji.video.poster,
json.kanji.video.mp4, json.kanji.strokes.count, json.kanji.onyomi.katakana, json.kanji.kunyomi.hiragana,
radical, examples);
}
// @ts-ignore
static SerializedObjectToKanji(obj): Kanji | null {
if (!obj) return null;
return new Kanji(obj.character, obj.meaning, obj.image, obj.animation, obj.strokes, obj.onyomi, obj.kunyomi, obj.radical, obj.examples);
}
}

@ -0,0 +1,4 @@
export type kanjiSearchParams = {
input: string
grades: string[]
}

@ -18,7 +18,6 @@ export default function KanjiStackNavigator() {
)
}
const stackOptions: StackNavigationOptions = {
headerShown: false,
presentation: "modal"

@ -0,0 +1,78 @@
import { NavigationContainer, useNavigation } from "@react-navigation/native";
import { createStackNavigator, StackNavigationOptions } from "@react-navigation/stack";
import React, { useEffect } from "react";
import { View, StyleSheet, Text } from "react-native";
import { useDispatch } from "react-redux";
import { fetchKanjis } from "../redux/thunks/fetchKanjis";
import TabBar from "./TabBar";
const stackOptions: StackNavigationOptions = {
headerShown: false,
presentation: "modal"
};
export const InitStack = () => {
const Stack = createStackNavigator();
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Startup"
screenOptions={stackOptions}
>
<Stack.Screen name="Startup" component={Startup} />
<Stack.Screen name="Main"
component={TabBar}
options={{ animationEnabled: false }} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default function Startup() {
const navigator = useNavigation();
const dispatch = useDispatch();
const init = async () => {
await dispatch(await fetchKanjis());
//await new Promise(resolve => setTimeout(resolve, 5000));
navigator.navigate("Main");
}
useEffect(() => {
init()
}, []);
return (
<View style={splashscreenStyle.container}>
<Text style={splashscreenStyle.title}>LEARNIHON</Text>
</View>
)
}
const splashscreenStyle = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
width: "100%",
height: "100%",
position: "absolute",
backgroundColor: '#FF5C5C',
},
title: {
color: 'white',
textAlign: 'center',
fontWeight: 'bold',
fontSize: 50,
}
})

@ -11,6 +11,7 @@ import { BookOpen as LearnIcon } from "react-native-feather";
import Learn from '../pages/Learn';
import Playground from '../pages/Playground';
import KanjiStackNavigator from './Stack';
import Header from '../components/Header';
@ -77,42 +78,43 @@ const TabBar = () => {
const Tab = createBottomTabNavigator();
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={tabOptions}
initialRouteName="Learn"
<>
<Header/>
<Tab.Navigator
screenOptions={tabOptions}
initialRouteName="Learn"
>
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<ListIcon color={color} />
)
}}
name="List"
component={KanjiStackNavigator}
/>
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<LearnIcon color={color} width={32} height={32} />
),
tabBarButton: (props) => (
<LearnButton { ... props}/>
)
}}
name="Learn"
component={Learn} />
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<PlaygroundIcon color={color} />
)
}}
name="Playground"
component={Playground} />
</Tab.Navigator>
</NavigationContainer>
>
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<ListIcon color={color} />
)
}}
name="List"
component={KanjiStackNavigator}
/>
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<LearnIcon color={color} width={32} height={32} />
),
tabBarButton: (props) => (
<LearnButton { ... props}/>
)
}}
name="Learn"
component={Learn} />
<Tab.Screen
options={{
tabBarIcon: ({ color }) => (
<PlaygroundIcon color={color} />
)
}}
name="Playground"
component={Playground} />
</Tab.Navigator>
</>
);
}

15
package-lock.json generated

@ -17,6 +17,7 @@
"@shopify/react-native-skia": "0.1.157",
"expo": "~47.0.12",
"expo-2d-context": "^0.0.3",
"expo-av": "~13.0.2",
"expo-cli": "^6.3.0",
"expo-status-bar": "~1.4.2",
"react": "18.1.0",
@ -8279,6 +8280,14 @@
"url-parse": "^1.5.9"
}
},
"node_modules/expo-av": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/expo-av/-/expo-av-13.0.2.tgz",
"integrity": "sha512-u+y9wUBodp08UjRZYckNWzr9zEQHK6eScRBkhdTd9Rq48SEZ7eN6xXn79hubvk0P5nj7lFS+hdKjmx9T5XHGww==",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-cli": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/expo-cli/-/expo-cli-6.3.0.tgz",
@ -24795,6 +24804,12 @@
"url-parse": "^1.5.9"
}
},
"expo-av": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/expo-av/-/expo-av-13.0.2.tgz",
"integrity": "sha512-u+y9wUBodp08UjRZYckNWzr9zEQHK6eScRBkhdTd9Rq48SEZ7eN6xXn79hubvk0P5nj7lFS+hdKjmx9T5XHGww==",
"requires": {}
},
"expo-cli": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/expo-cli/-/expo-cli-6.3.0.tgz",

@ -31,7 +31,8 @@
"react-native-web": "~0.18.9",
"react-redux": "^8.0.5",
"redux": "^4.2.1",
"rn-perfect-sketch-canvas": "^0.3.0"
"rn-perfect-sketch-canvas": "^0.3.0",
"expo-av": "~13.0.2"
},
"devDependencies": {
"@babel/core": "^7.12.9",

@ -1,24 +1,21 @@
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, useColorScheme, FlatList, ScrollView } from 'react-native';
import KanjiListCell from '../components/KanjiListCell';
import { Kanji, KanjiMapper } from '../model/kanji';
import Video from 'react-native-video';
import { SvgXml } from 'react-native-svg';
import KanjiPlaygroundList from '../components/KanjiPlaygroundList';
import Svg, { Defs, LinearGradient, Mask, Path, Rect, SvgUri, SvgXml, Text as SvgText } from 'react-native-svg';
import DetailExamples from '../components/DetailExamples';
import DetailRadical from '../components/DetailRadical';
import { Kanji } from '../model/kanji';
const Detail = ({route}) => {
const kanji_temp = route.params.kanji;
const kanji:Kanji = route.params.kanji;
const detailStyle = useColorScheme() == 'light' ? detailStyle_light : detailStyle_dark;
const [imgXml, setImgXml] = useState('<svg></svg>');
const [iconXml, setIconXml] = useState('<svg></svg>');
const [imgXml, setImgXml] = useState('<svg></svg>');
const fetchXml = async () => {
const xml = await (await fetch(kanji.image)).text();
const imgxml = await (await fetch(kanji.image)).text();
setImgXml(imgxml);
const iconxml = await (await fetch(kanji.radical.position)).text();
setImgXml(xml);
setIconXml(iconxml);
}
@ -26,222 +23,19 @@ const Detail = ({route}) => {
fetchXml();
}, []);
const kanji = KanjiMapper.ApiJsonToKanji({
"kanji": {
"character": "所",
"meaning": {
"english": "place"
},
"strokes": {
"count": 8,
"timings": [
0,
1.465,
2.598,
3.465,
4.332,
5.332,
6.265,
7.198,
8.533333
],
"images": [
"https://media.kanjialive.com/kanji_strokes/(ba)sho_1.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_2.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_3.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_4.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_5.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_6.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_7.svg",
"https://media.kanjialive.com/kanji_strokes/(ba)sho_8.svg"
]
},
"onyomi": {
"romaji": "sho",
"katakana": "ショ"
},
"kunyomi": {
"romaji": "tokoro",
"hiragana": "ところ"
},
"video": {
"poster": "https://media.kanjialive.com/kanji_strokes/(ba)sho_8.svg",
"mp4": "https://media.kanjialive.com/kanji_animations/kanji_mp4/(ba)sho_00.mp4",
"webm": "https://media.kanjialive.com/kanji_animations/kanji_webm/(ba)sho_00.webm"
}
},
"radical": {
"character": "⼧",
"strokes": 4,
"image": "https://media.kanjialive.com/radical_character/todare.svg",
"position": {
"hiragana": "たれ",
"romaji": "tare",
"icon": "https://media.kanjialive.com/rad_positions/tare.svg"
},
"name": {
"hiragana": "とだれ",
"romaji": "todare"
},
"meaning": {
"english": "door"
},
"animation": [
"https://media.kanjialive.com/rad_frames/todare0.svg",
"https://media.kanjialive.com/rad_frames/todare1.svg",
"https://media.kanjialive.com/rad_frames/todare2.svg"
]
},
"references": {
"grade": 3,
"kodansha": "568",
"classic_nelson": "1821"
},
"examples": [
{
"japanese": "場所(ばしょ)",
"meaning": {
"english": "place"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_a.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_a.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_a.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_a.mp3"
}
},
{
"japanese": "住所(じゅうしょ)",
"meaning": {
"english": "address"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_b.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_b.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_b.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_b.mp3"
}
},
{
"japanese": "近所(きんじょ)",
"meaning": {
"english": "neighborhood"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_c.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_c.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_c.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_c.mp3"
}
},
{
"japanese": "役所(やくしょ)",
"meaning": {
"english": "public office"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_d.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_d.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_d.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_d.mp3"
}
},
{
"japanese": "名所(めいしょ)",
"meaning": {
"english": "famous place"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_e.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_e.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_e.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_e.mp3"
}
},
{
"japanese": "研究所(けんきゅうじょ)",
"meaning": {
"english": "research institute"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_f.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_f.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_f.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_f.mp3"
}
},
{
"japanese": "停留所(ていりゅうじょ)",
"meaning": {
"english": "bus stop"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_g.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_g.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_g.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_g.mp3"
}
},
{
"japanese": "所有する(しょゆうする)",
"meaning": {
"english": "possess"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_h.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_h.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_h.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_h.mp3"
}
},
{
"japanese": "所(ところ)",
"meaning": {
"english": "place"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_i.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_i.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_i.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_i.mp3"
}
},
{
"japanese": "台所(だいどころ)",
"meaning": {
"english": "kitchen"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_j.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_j.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_j.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_j.mp3"
}
},
{
"japanese": "居所(いどころ)",
"meaning": {
"english": "whereabouts"
},
"audio": {
"opus": "https://media.kanjialive.com/examples_audio/audio-opus/(ba)sho_06_k.opus",
"aac": "https://media.kanjialive.com/examples_audio/audio-aac/(ba)sho_06_k.aac",
"ogg": "https://media.kanjialive.com/examples_audio/audio-ogg/(ba)sho_06_k.ogg",
"mp3": "https://media.kanjialive.com/examples_audio/audio-mp3/(ba)sho_06_k.mp3"
}
}
]
})
return (
<View style={detailStyle.container}>
<Text style={detailStyle.text}>{kanji.onyomi}</Text>
<Text style={detailStyle.text}>{kanji.kunyomi}</Text>
<SvgXml
xml={imgXml
.replace(/fill="#[0-9a-f]{6}"/g, `fill=${detailStyle.svg.color}`)}
width="100"
height="100" />
<View>
<Text style={detailStyle.text}>{kanji.onyomi}</Text>
<Text style={detailStyle.text}>{kanji.kunyomi}</Text>
</View>
<SvgXml
xml={imgXml
.replace(/fill="#[0-9a-f]{6}"/g, `fill=${detailStyle.svg.color}`)}
width="100"
height="100"/>
<Text style={detailStyle.tinyText}>{kanji.strokes + " strokes"}</Text>
<Text style={detailStyle.meaningText}>{kanji.meaning}</Text>
@ -262,6 +56,8 @@ const detailStyle_light = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "#e4e4e4",
height: "100%",
width: "100%"
},
svg: {
color: "black"
@ -278,7 +74,7 @@ const detailStyle_light = StyleSheet.create({
color: "black"
},
meaningText: {
fontSize: 50,
fontSize: 20,
color: "#FF5C5C",
fontWeight: "900",
@ -309,7 +105,7 @@ const detailStyle_dark = StyleSheet.create({
color: "white"
},
meaningText: {
fontSize: 50,
fontSize: 20,
color: "#FF5C5C",
fontWeight: "900",

@ -1,13 +1,15 @@
import React from 'react';
import { View, StyleSheet, useColorScheme } from 'react-native';
import { useSelector } from 'react-redux';
import KanjiCard from '../components/KanjiCard';
import { Kanji } from '../model/kanji';
import { KanjiListByGrade } from '../model/kanjiListByGrades';
const Learn = () => {
const learnStyle = useColorScheme() == 'light' ? learnStyle_light : learnStyle_dark;
return (
<View style={learnStyle.container}>
<KanjiCard kanji="所"></KanjiCard>

@ -13,9 +13,9 @@ const Playground = () => {
return (
<View style={playgroundStyle.container}>
<KanjiPlaygroundList data={[
new Kanji("親", "parent", "https://media.kanjialive.com/kanji_strokes/shita(shii)_16.svg"),
new Kanji("雨", "rain", "https://media.kanjialive.com/kanji_strokes/u-ame_8.svg"),
new Kanji("貴", "noble", "https://media.kanjialive.com/kanji_strokes/ki-touto(i)_12.svg")
new Kanji("親", "parent", "https://media.kanjialive.com/kanji_strokes/shita(shii)_16.svg", "aaa", 16),
new Kanji("雨", "rain", "https://media.kanjialive.com/kanji_strokes/u-ame_8.svg", "aaa", 8),
new Kanji("貴", "noble", "https://media.kanjialive.com/kanji_strokes/ki-touto(i)_12.svg", "aaa", 12)
]} />
<DrawingCanva backgroundImage="https://media.kanjialive.com/kanji_strokes/otozu(reru)_11.svg"/>
</View>

@ -1,9 +0,0 @@
import { Kanji } from '../../model/kanji';
import { FETCH_KANJIS } from '../constants';
export const fetchKanjis = (kanjis: Kanji[]) => {
return {
type: FETCH_KANJIS,
payload: kanjis,
};
}

@ -0,0 +1,10 @@
import { Kanji } from '../../model/kanji';
import { SET_KANJIS } from '../constants';
import { KanjiListByGrade } from '../../model/kanjiListByGrades';
export const setKanjis = (kanjis: kanjiListByGrade) => {
return {
type: SET_KANJIS,
payload: kanjis,
};
}

@ -1,2 +1,2 @@
export const FETCH_KANJIS = 'FETCH_KANJIS';
export const SET_SELECTED_KANJI = 'SET_SELECTED_KANJI';
export const SET_SELECTED_KANJI = 'SET_SELECTED_KANJI';
export const SET_KANJIS = 'SET_KANJIS';

@ -1,19 +1,19 @@
import { initKanjiListByGrade } from '../../model/kanjiListByGrades';
import * as c from '../constants';
const initialState = {
kanjis: [],
selectedKanji: null
kanjis: initKanjiListByGrade(),
selectedKanji: null,
}
// @ts-ignore
export default function kanjiReducer(state = initialState, action) {
switch (action.type) {
case c.FETCH_KANJIS:
case c.SET_KANJIS:
// @ts-ignore
return { ...state, kanjis: state.kanjis.push(action.payload) };
return { ...state, kanjis: action.payload };
case c.SET_SELECTED_KANJI:
// @ts-ignore
console.log("select" + action.payload.meaning);
return { ...state, selectedKanji: action.payload };
default:
return state;

@ -1,4 +1,4 @@
import { configureStore } from '@reduxjs/toolkit'
import { configureStore, createSerializableStateInvariantMiddleware } from '@reduxjs/toolkit'
import kanjiReducer from './reducers/kanjiReducer';
// Reference here all your application reducers
@ -7,8 +7,9 @@ const reducer = {
}
// @ts-ignore
const store = configureStore({
reducer
},);
const store = configureStore(
{
reducer,
},);
export default store;

@ -0,0 +1,40 @@
import { initKanjiListByGrade, KanjiListByGrade } from '../../model/kanjiListByGrades';
import { KanjiMapper } from '../../model/kanjiMapper';
import { setKanjis } from '../actions/setKanjis';
// @ts-ignore
export const fetchKanjis = async () => {
const kanjis: KanjiListByGrade = initKanjiListByGrade();
const options = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '19516a9900mshce10de76f99976bp10f192jsn8c8d82222baa',
'X-RapidAPI-Host': 'kanjialive-api.p.rapidapi.com'
}
};
const fetchData = async (grade: string) => {
return fetch(`https://kanjialive-api.p.rapidapi.com/api/public/search/advanced/?grade=${grade}`, options)
}
return async dispatch => {
const fetchAll = async () => {
for (let i = 1; i <= 6; i++) {
await fetchData(i.toString()).then(async (response) => {
const data = await response.json();
data.forEach(async (it: object) => {
await fetch(`https://kanjialive-api.p.rapidapi.com/api/public/kanji/${it.kanji.character}`, options)
.then(async detail => {
const detail_data = await detail.json();
kanjis['Grade ' + i].push(KanjiMapper.ApiJsonToKanji(detail_data));
})
})
})
}
}
return fetchAll().then(_ => dispatch(setKanjis(kanjis)))
.catch((err) => console.log("ERR : " + err));
};
}
Loading…
Cancel
Save