diff --git a/.vs/LEARNIHON/FileContentIndex/213f307e-f081-4fb3-9ac0-4054a8ed3e37.vsidx b/.vs/LEARNIHON/FileContentIndex/213f307e-f081-4fb3-9ac0-4054a8ed3e37.vsidx new file mode 100644 index 0000000..a4d44d3 Binary files /dev/null and b/.vs/LEARNIHON/FileContentIndex/213f307e-f081-4fb3-9ac0-4054a8ed3e37.vsidx differ diff --git a/.vs/LEARNIHON/FileContentIndex/2b0208b9-9a28-4375-82d1-eb4bab1547be.vsidx b/.vs/LEARNIHON/FileContentIndex/2b0208b9-9a28-4375-82d1-eb4bab1547be.vsidx new file mode 100644 index 0000000..cace8c7 Binary files /dev/null and b/.vs/LEARNIHON/FileContentIndex/2b0208b9-9a28-4375-82d1-eb4bab1547be.vsidx differ diff --git a/.vs/LEARNIHON/FileContentIndex/86a5d04e-dea0-42e2-8ec2-438527b31680.vsidx b/.vs/LEARNIHON/FileContentIndex/86a5d04e-dea0-42e2-8ec2-438527b31680.vsidx deleted file mode 100644 index bff553d..0000000 Binary files a/.vs/LEARNIHON/FileContentIndex/86a5d04e-dea0-42e2-8ec2-438527b31680.vsidx and /dev/null differ diff --git a/.vs/LEARNIHON/FileContentIndex/f55ad326-8870-45ff-ae39-c7eafff83a9c.vsidx b/.vs/LEARNIHON/FileContentIndex/f55ad326-8870-45ff-ae39-c7eafff83a9c.vsidx new file mode 100644 index 0000000..a944b4f Binary files /dev/null and b/.vs/LEARNIHON/FileContentIndex/f55ad326-8870-45ff-ae39-c7eafff83a9c.vsidx differ diff --git a/.vs/LEARNIHON/v17/.wsuo b/.vs/LEARNIHON/v17/.wsuo index c750d8c..eb4cc5a 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 275482b..d63fa1d 100644 --- a/.vs/VSWorkspaceState.json +++ b/.vs/VSWorkspaceState.json @@ -11,6 +11,6 @@ "\\redux\\reducers", "\\redux\\thunks" ], - "SelectedNode": "\\assets", + "SelectedNode": "\\components\\KanjiPlaygroundList.tsx", "PreviewInSolutionExplorer": false } \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite index 22a1db1..a8523a0 100644 Binary files a/.vs/slnx.sqlite and b/.vs/slnx.sqlite differ diff --git a/assets/colors.ts b/assets/colors.ts index b825a73..21f9787 100644 --- a/assets/colors.ts +++ b/assets/colors.ts @@ -7,5 +7,6 @@ export const learnihonColors = { "dark_2": "#1C1C1C", "dark_3": "#0D0D0D", "wrong": "#AA3D3D", - "correct": "#3DAA3D" + "correct": "#3DAA3D", + "warning": "#FAD63C" } \ No newline at end of file diff --git a/components/KanjiCard.tsx b/components/KanjiCard.tsx index 2eefe90..44f1bd0 100644 --- a/components/KanjiCard.tsx +++ b/components/KanjiCard.tsx @@ -5,6 +5,7 @@ import { useSelector } from 'react-redux'; import { learnihonColors } from '../assets/colors'; import { Kanji } from '../model/kanji'; import { KanjiListByGrade } from '../model/kanjiListByGrades'; +import { storeGuess } from '../storage/storage'; import GradeChipList from './GradeChipList'; import KanjiAnswerField from './KanjiAnswerField'; @@ -68,7 +69,9 @@ const KanjiCard = () => { }, [kanji]); const computeAnswer = () => { - setAnswerTextColor(isAnswerRight() ? learnihonColors.correct : learnihonColors.wrong); + var isCorrect = isAnswerRight(); + setAnswerTextColor(isCorrect ? learnihonColors.correct : learnihonColors.wrong); + storeGuess(kanji?.character!, isCorrect); setHasAnswered(true); } const computeNext = () => { diff --git a/components/KanjiListCell.tsx b/components/KanjiListCell.tsx index e00decb..9a44d5f 100644 --- a/components/KanjiListCell.tsx +++ b/components/KanjiListCell.tsx @@ -1,8 +1,10 @@ import { useNavigation } from '@react-navigation/native'; -import React from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, useColorScheme } from 'react-native'; import { learnihonColors } from '../assets/colors'; import { Kanji } from '../model/kanji'; +import { calcCorrectGuessesRatio, getColorByRatio, KanjiGuess } from '../model/kanjiGuess'; +import { retrieveGuess } from '../storage/storage'; interface kanjiListCellProps { kanji: Kanji; @@ -13,11 +15,34 @@ const KanjiListCell = React.memo((props: kanjiListCellProps) => { const cellStyle = useColorScheme() == 'light' ? cellStyle_light : cellStyle_dark; const navigator = useNavigation(); + const [guessColor, setGuessColor] = useState(cellStyle.text.color); + const [ratio, setRatio] = useState(-1); + var ratioStyle = StyleSheet.create({ + text: + { + color: guessColor, + textAlign: "right", + fontWeight: "bold", + position: "absolute", + right: 15 + } + }); + + const memoizedValues = useMemo(async () => { + const guess = await retrieveGuess(props.kanji.character); + const ratio = guess ? await calcCorrectGuessesRatio(guess) : -1; + setRatio(ratio); + }, []); + + useEffect(() => { + setGuessColor(getColorByRatio(ratio)); + }, [ratio]); return ( navigator.navigate("Detail", {"kanji": props.kanji})} style={cellStyle.item}> {props.kanji.character} {props.kanji.meaning} + {ratio!=-1 && ({ratio}%)} ); }); @@ -34,7 +59,6 @@ const cellStyle_light = StyleSheet.create({ }, text: { color: "black", - width: "90%" }, kanji: { fontWeight: "bold", @@ -56,14 +80,13 @@ const cellStyle_dark = StyleSheet.create({ }, text: { color: "white", - width: "90%" }, kanji: { fontWeight: "bold", color: "white", fontSize: "20em", - width: "10%" + width: "10%", }, }) - + export default KanjiListCell; \ No newline at end of file diff --git a/model/kanjiGuess.ts b/model/kanjiGuess.ts new file mode 100644 index 0000000..6f221d9 --- /dev/null +++ b/model/kanjiGuess.ts @@ -0,0 +1,14 @@ +import { learnihonColors } from "../assets/colors"; + +export type KanjiGuess = { + totalGuesses: number, + totalCorrectGuesses: number +} + +export const calcCorrectGuessesRatio = (guess: KanjiGuess): number => { + return (guess.totalCorrectGuesses / guess.totalGuesses) * 100; +} + +export const getColorByRatio = (ratio: number): string => { + return ratio <= 33 ? learnihonColors.wrong : ratio <= 66 ? learnihonColors.warning : learnihonColors.correct; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f458406..4a13a67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@benjeau/react-native-draw": "^0.8.3", + "@react-native-async-storage/async-storage": "~1.17.3", "@react-native-community/slider": "4.2.4", "@react-navigation/bottom-tabs": "^6.5.4", "@react-navigation/native": "^6.1.3", @@ -3778,6 +3779,17 @@ "node": ">=10" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.11.tgz", + "integrity": "sha512-bzs45n5HNcDq6mxXnSsOHysZWn1SbbebNxldBXCQs8dSvF8Aor9KCdpm+TpnnGweK3R6diqsT8lFhX77VX0NFw==", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || 0.60 - 0.71 || 1000.0.0" + } + }, "node_modules/@react-native-community/cli": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-9.2.1.tgz", @@ -12133,6 +12145,25 @@ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -21521,6 +21552,14 @@ } } }, + "@react-native-async-storage/async-storage": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.11.tgz", + "integrity": "sha512-bzs45n5HNcDq6mxXnSsOHysZWn1SbbebNxldBXCQs8dSvF8Aor9KCdpm+TpnnGweK3R6diqsT8lFhX77VX0NFw==", + "requires": { + "merge-options": "^3.0.4" + } + }, "@react-native-community/cli": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-9.2.1.tgz", @@ -27536,6 +27575,21 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "requires": { + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 4d9608f..4935902 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,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", + "@react-native-async-storage/async-storage": "~1.17.3" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/storage/storage.ts b/storage/storage.ts new file mode 100644 index 0000000..8d8422c --- /dev/null +++ b/storage/storage.ts @@ -0,0 +1,43 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { KanjiGuess } from '../model/kanjiGuess' + +export const retrieveGuess = async (kanji: string) => { + try { + const value = await AsyncStorage.getItem(kanji); + if (value === null) { + return null; + } + const guess: KanjiGuess = await JSON.parse(value!) + return guess; + } catch (error) { + console.log(error) + return null; + } +}; + +export const storeGuess = async (kanji: string, wasRight: boolean) => { + + const guess = await retrieveGuess(kanji) + if (guess === null) { + try { + await AsyncStorage.setItem( + kanji, + JSON.stringify({ totalGuesses: 1, totalCorrectGuesses: wasRight ? 1 : 0 }), + ); + } catch (error) { + console.log(error) + } + } else { + try { + await AsyncStorage.setItem( + kanji, + JSON.stringify({ + totalGuesses: guess.totalGuesses + 1, + totalCorrectGuesses: wasRight ? guess.totalCorrectGuesses + 1 : guess.totalCorrectGuesses + }), + ); + } catch (error) { + console.log(error) + } + } +};