diff --git a/.vs/LEARNIHON/FileContentIndex/18ab66a5-4a11-4328-bc91-cd3376b0ec91.vsidx b/.vs/LEARNIHON/FileContentIndex/18ab66a5-4a11-4328-bc91-cd3376b0ec91.vsidx
deleted file mode 100644
index 049725d..0000000
Binary files a/.vs/LEARNIHON/FileContentIndex/18ab66a5-4a11-4328-bc91-cd3376b0ec91.vsidx and /dev/null differ
diff --git a/.vs/LEARNIHON/FileContentIndex/985f779d-3e71-41e7-8e72-34b885e698a7.vsidx b/.vs/LEARNIHON/FileContentIndex/985f779d-3e71-41e7-8e72-34b885e698a7.vsidx
new file mode 100644
index 0000000..1fcabfc
Binary files /dev/null and b/.vs/LEARNIHON/FileContentIndex/985f779d-3e71-41e7-8e72-34b885e698a7.vsidx differ
diff --git a/.vs/LEARNIHON/FileContentIndex/a731465e-eb3c-499c-8b0d-31f641e360d5.vsidx b/.vs/LEARNIHON/FileContentIndex/a731465e-eb3c-499c-8b0d-31f641e360d5.vsidx
deleted file mode 100644
index 4954627..0000000
Binary files a/.vs/LEARNIHON/FileContentIndex/a731465e-eb3c-499c-8b0d-31f641e360d5.vsidx and /dev/null differ
diff --git a/.vs/LEARNIHON/FileContentIndex/435d2972-bbd4-45e0-8354-cf69c2c152c7.vsidx b/.vs/LEARNIHON/FileContentIndex/ae2e6dd4-98b8-42d0-8ba6-f2bd60e1aa88.vsidx
similarity index 53%
rename from .vs/LEARNIHON/FileContentIndex/435d2972-bbd4-45e0-8354-cf69c2c152c7.vsidx
rename to .vs/LEARNIHON/FileContentIndex/ae2e6dd4-98b8-42d0-8ba6-f2bd60e1aa88.vsidx
index 5150a6d..6674bf8 100644
Binary files a/.vs/LEARNIHON/FileContentIndex/435d2972-bbd4-45e0-8354-cf69c2c152c7.vsidx and b/.vs/LEARNIHON/FileContentIndex/ae2e6dd4-98b8-42d0-8ba6-f2bd60e1aa88.vsidx differ
diff --git a/.vs/LEARNIHON/FileContentIndex/c4f4ae09-c384-4dd2-87f4-19f2615f5a9c.vsidx b/.vs/LEARNIHON/FileContentIndex/c4f4ae09-c384-4dd2-87f4-19f2615f5a9c.vsidx
new file mode 100644
index 0000000..7060fc4
Binary files /dev/null and b/.vs/LEARNIHON/FileContentIndex/c4f4ae09-c384-4dd2-87f4-19f2615f5a9c.vsidx differ
diff --git a/.vs/LEARNIHON/v17/.wsuo b/.vs/LEARNIHON/v17/.wsuo
index 2f2f413..17ea5fa 100644
Binary files a/.vs/LEARNIHON/v17/.wsuo and b/.vs/LEARNIHON/v17/.wsuo differ
diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json
index 1048f14..834b9e4 100644
--- a/.vs/VSWorkspaceState.json
+++ b/.vs/VSWorkspaceState.json
@@ -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
}
\ No newline at end of file
diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite
index feede14..baf150b 100644
Binary files a/.vs/slnx.sqlite and b/.vs/slnx.sqlite differ
diff --git a/App.tsx b/App.tsx
index b9c4b88..32822ee 100644
--- a/App.tsx
+++ b/App.tsx
@@ -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 (
- { Keyboard.dismiss(); }}>
-
-
-
-
-
-
+ { Keyboard.dismiss(); }}>
+
+
+
+
+
);
diff --git a/assets/answerAnimation.ts b/assets/animations/answerAnimation.ts
similarity index 98%
rename from assets/answerAnimation.ts
rename to assets/animations/answerAnimation.ts
index a1dc765..9cd181c 100644
--- a/assets/answerAnimation.ts
+++ b/assets/animations/answerAnimation.ts
@@ -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]
}),
},
],
diff --git a/components/DetailExamples.tsx b/components/DetailExamples.tsx
index a923f99..c430e97 100644
--- a/components/DetailExamples.tsx
+++ b/components/DetailExamples.tsx
@@ -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,
diff --git a/components/DrawingCanva.tsx b/components/DrawingCanva.tsx
index c58c186..d2dae57 100644
--- a/components/DrawingCanva.tsx
+++ b/components/DrawingCanva.tsx
@@ -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(null);
const [strokeWidth, setStroke] = useState(5);
const [isCanvasReady, setIsCanvasReady] = useState(false);
const [imgXml, setImgXml] = useState('');
-
+ 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 (
{selectedKanji && (
@@ -49,17 +53,18 @@ const DrawingCanva = (props: DrawingCanvaProps) => {
opacity={0.1}
style={style.back}
/>)}
-
+
+
setStroke(val)}
minimumValue={5}
- maximumValue={10}
+ maximumValue={15}
minimumTrackTintColor={"#FF5C5C"}
/>
{isCanvasReady && (
diff --git a/components/GradeChip.tsx b/components/GradeChip.tsx
new file mode 100644
index 0000000..8b36830
--- /dev/null
+++ b/components/GradeChip.tsx
@@ -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 (
+ { select() }}>
+ Grade {props.grade}
+ {isSelected && ()}
+
+ );
+};
+
+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;
\ No newline at end of file
diff --git a/components/KanjiAnswerField.tsx b/components/KanjiAnswerField.tsx
index 7d0b136..84fdfb2 100644
--- a/components/KanjiAnswerField.tsx
+++ b/components/KanjiAnswerField.tsx
@@ -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 = () => {
diff --git a/components/KanjiCard.tsx b/components/KanjiCard.tsx
index 9987701..5154b8f 100644
--- a/components/KanjiCard.tsx
+++ b/components/KanjiCard.tsx
@@ -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('');
+
+ 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 (
- {loading ? Loading... : {res.kanji.onyomi.katakana}}
+ {loading ? Loading... : {kanji?.onyomi}}
{!loading && (
{
height="200"
/>
)}
- {loading ? : {res.kanji.meaning.english}}
+ {loading ? : {kanji?.meaning}}
diff --git a/components/KanjiList.tsx b/components/KanjiList.tsx
index 57961c5..8ead142 100644
--- a/components/KanjiList.tsx
+++ b/components/KanjiList.tsx
@@ -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 (
-
-
+
+
}
renderSectionHeader={({ section }) => (
{section.title}
)}
- keyExtractor={item => `basicListEntry-${item.character}`}
+ keyExtractor={item => `basicListEntry-${item.character}`
+ }
+ style={kanjiListStyle.list}
>
+
+
);
-};
+};
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"
+ },
})
diff --git a/components/KanjiListCell.tsx b/components/KanjiListCell.tsx
index bde0e8b..83099e1 100644
--- a/components/KanjiListCell.tsx
+++ b/components/KanjiListCell.tsx
@@ -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%"
},
})
diff --git a/components/KanjiListSearchPanel.tsx b/components/KanjiListSearchPanel.tsx
new file mode 100644
index 0000000..df34709
--- /dev/null
+++ b/components/KanjiListSearchPanel.tsx
@@ -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 (
+
+
+ }
+ horizontal={true}
+ showsHorizontalScrollIndicator={false}
+
+ >
+
+
+ );
+};
+
+
+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;
\ No newline at end of file
diff --git a/components/KanjiPlaygroundList.tsx b/components/KanjiPlaygroundList.tsx
index b88a16b..c033b61 100644
--- a/components/KanjiPlaygroundList.tsx
+++ b/components/KanjiPlaygroundList.tsx
@@ -27,7 +27,7 @@ const KanjiPlaygroundList = (props: kanjiPlaygroundListProps) => {
data={props.data}
renderItem={
({ item }) => (
- { dispatch(setSelectedKanji(item)); console.log(item) }} style={kanjiPlaygroundList.entry}>
+ { dispatch(setSelectedKanji(item))}} style={kanjiPlaygroundList.entry}>
{item.character}
)
diff --git a/model/kanji.ts b/model/kanji.ts
index b5d2b91..1b0976a 100644
--- a/model/kanji.ts
+++ b/model/kanji.ts
@@ -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);
- }
-
-}
-
diff --git a/model/kanjiListByGrades.ts b/model/kanjiListByGrades.ts
new file mode 100644
index 0000000..a1b8489
--- /dev/null
+++ b/model/kanjiListByGrades.ts
@@ -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": [],
+ }
+}
\ No newline at end of file
diff --git a/model/kanjiMapper.ts b/model/kanjiMapper.ts
new file mode 100644
index 0000000..4ae4c5e
--- /dev/null
+++ b/model/kanjiMapper.ts
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/model/kanjiSearchParams.ts b/model/kanjiSearchParams.ts
new file mode 100644
index 0000000..a61ed99
--- /dev/null
+++ b/model/kanjiSearchParams.ts
@@ -0,0 +1,4 @@
+export type kanjiSearchParams = {
+ input: string
+ grades: string[]
+}
\ No newline at end of file
diff --git a/navigation/Stack.tsx b/navigation/Stack.tsx
index d3eeb69..a9f01ae 100644
--- a/navigation/Stack.tsx
+++ b/navigation/Stack.tsx
@@ -18,7 +18,6 @@ export default function KanjiStackNavigator() {
)
}
-
const stackOptions: StackNavigationOptions = {
headerShown: false,
presentation: "modal"
diff --git a/navigation/Startup.tsx b/navigation/Startup.tsx
new file mode 100644
index 0000000..16d53a1
--- /dev/null
+++ b/navigation/Startup.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+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 (
+
+ LEARNIHON
+
+ )
+
+}
+
+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,
+ }
+})
+
+
diff --git a/navigation/TabBar.tsx b/navigation/TabBar.tsx
index 58609e6..aa9795e 100644
--- a/navigation/TabBar.tsx
+++ b/navigation/TabBar.tsx
@@ -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 (
-
-
+
+
- (
-
- )
- }}
- name="List"
- component={KanjiStackNavigator}
- />
- (
-
- ),
- tabBarButton: (props) => (
-
- )
- }}
- name="Learn"
- component={Learn} />
- (
-
- )
- }}
- name="Playground"
- component={Playground} />
-
-
+ >
+ (
+
+ )
+ }}
+ name="List"
+ component={KanjiStackNavigator}
+ />
+ (
+
+ ),
+ tabBarButton: (props) => (
+
+ )
+ }}
+ name="Learn"
+ component={Learn} />
+ (
+
+ )
+ }}
+ name="Playground"
+ component={Playground} />
+
+ >
);
}
diff --git a/package-lock.json b/package-lock.json
index 3bf5f75..f458406 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 872b317..dd187e4 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pages/Detail.tsx b/pages/Detail.tsx
index 676040d..1d613c9 100644
--- a/pages/Detail.tsx
+++ b/pages/Detail.tsx
@@ -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('');
const [iconXml, setIconXml] = useState('');
+ const [imgXml, setImgXml] = useState('');
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 (
- {kanji.onyomi}
- {kanji.kunyomi}
-
+
+ {kanji.onyomi}
+ {kanji.kunyomi}
+
+
+
{kanji.strokes + " strokes"}
{kanji.meaning}
@@ -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",
diff --git a/pages/Learn.tsx b/pages/Learn.tsx
index 08733ed..d982c7c 100644
--- a/pages/Learn.tsx
+++ b/pages/Learn.tsx
@@ -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 (
diff --git a/pages/Playground.tsx b/pages/Playground.tsx
index 2a988cc..8526f6c 100644
--- a/pages/Playground.tsx
+++ b/pages/Playground.tsx
@@ -13,9 +13,9 @@ const Playground = () => {
return (
diff --git a/redux/actions/fetchKanjis.ts b/redux/actions/fetchKanjis.ts
deleted file mode 100644
index a0746ee..0000000
--- a/redux/actions/fetchKanjis.ts
+++ /dev/null
@@ -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,
- };
-}
\ No newline at end of file
diff --git a/redux/actions/setKanjis.ts b/redux/actions/setKanjis.ts
new file mode 100644
index 0000000..fa7be81
--- /dev/null
+++ b/redux/actions/setKanjis.ts
@@ -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,
+ };
+}
\ No newline at end of file
diff --git a/redux/constants.ts b/redux/constants.ts
index 36c2b52..f82bfd9 100644
--- a/redux/constants.ts
+++ b/redux/constants.ts
@@ -1,2 +1,2 @@
-export const FETCH_KANJIS = 'FETCH_KANJIS';
-export const SET_SELECTED_KANJI = 'SET_SELECTED_KANJI';
\ No newline at end of file
+export const SET_SELECTED_KANJI = 'SET_SELECTED_KANJI';
+export const SET_KANJIS = 'SET_KANJIS';
\ No newline at end of file
diff --git a/redux/reducers/kanjiReducer.ts b/redux/reducers/kanjiReducer.ts
index a0d7ba1..2f243a0 100644
--- a/redux/reducers/kanjiReducer.ts
+++ b/redux/reducers/kanjiReducer.ts
@@ -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;
diff --git a/redux/store.ts b/redux/store.ts
index 73c0fe8..9ce2376 100644
--- a/redux/store.ts
+++ b/redux/store.ts
@@ -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;
\ No newline at end of file
diff --git a/redux/thunks/fetchKanjis.ts b/redux/thunks/fetchKanjis.ts
new file mode 100644
index 0000000..5a69c13
--- /dev/null
+++ b/redux/thunks/fetchKanjis.ts
@@ -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));
+
+ };
+}
\ No newline at end of file