Skip to content

Commit a8539a1

Browse files
committed
add grammar search
1 parent abcb294 commit a8539a1

17 files changed

+563
-177
lines changed

.gitignore

+42-5
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,50 @@ yarn-error.*
4242
# typescript
4343
*.tsbuildinfo
4444

45-
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
46-
# The following patterns were generated by expo-cli
47-
48-
expo-env.d.ts
49-
# @end expo-cli
5045

5146
scripts/data
5247

5348
# ignore ios
5449
ios/
50+
51+
# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
52+
# The following patterns were generated by expo-cli
53+
54+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
55+
56+
# dependencies
57+
node_modules/
58+
59+
# Expo
60+
.expo/
61+
dist/
62+
web-build/
63+
64+
# Native
65+
*.orig.*
66+
*.jks
67+
*.p8
68+
*.p12
69+
*.key
70+
*.mobileprovision
71+
72+
# Metro
73+
.metro-health-check*
74+
75+
# debug
76+
npm-debug.*
77+
yarn-debug.*
78+
yarn-error.*
79+
80+
# macOS
81+
.DS_Store
82+
*.pem
83+
84+
# local env files
85+
.env*.local
86+
87+
# typescript
88+
*.tsbuildinfo
89+
90+
expo-env.d.ts
91+
# @end expo-cli

.yarn/install-state.gz

7.95 KB
Binary file not shown.

app.json

+13-4
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,35 @@
1212
"resizeMode": "contain",
1313
"backgroundColor": "#ffffff"
1414
},
15-
"assetBundlePatterns": ["**/*"],
15+
"assetBundlePatterns": [
16+
"**/*"
17+
],
1618
"ios": {
1719
"supportsTablet": true,
1820
"bundleIdentifier": "dev.mmtf.jlpt-practice",
1921
"infoPlist": {
20-
"LSApplicationQueriesSchemes": ["shirabelookup"]
22+
"LSApplicationQueriesSchemes": [
23+
"shirabelookup"
24+
]
2125
}
2226
},
2327
"android": {
2428
"adaptiveIcon": {
2529
"foregroundImage": "./assets/images/adaptive-icon.png",
2630
"backgroundColor": "#ffffff"
27-
}
31+
},
32+
"package": "com.anonymous.jlptpractice"
2833
},
2934
"web": {
3035
"bundler": "metro",
3136
"output": "static",
3237
"favicon": "./assets/images/favicon.png"
3338
},
34-
"plugins": ["expo-router", "expo-build-properties", "expo-font"],
39+
"plugins": [
40+
"expo-router",
41+
"expo-build-properties",
42+
"expo-font"
43+
],
3544
"experiments": {
3645
"typedRoutes": true
3746
}

app/(tabs)/grammar.tsx

+44-23
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { ScrollView, XStack } from "tamagui";
1+
import { Input, ScrollView, useMedia, XStack } from "tamagui";
22
import { useRouter } from "expo-router";
33

44
import { ListItem, Text } from "tamagui";
55
import { useRoute } from "@react-navigation/native";
66
import Details from "../../scripts/data/grammar_details.json";
7-
import { BookKey, ChevronRight } from "@tamagui/lucide-icons";
7+
import { BookKey, ChevronRight, Search } from "@tamagui/lucide-icons";
88
import { FlatList } from "react-native";
9+
import { useMemo, useState } from "react";
910

1011
const LevelComp = ({ level }: { level: string }) => {
1112
return (
@@ -19,27 +20,47 @@ const LevelComp = ({ level }: { level: string }) => {
1920
};
2021
export default function () {
2122
const r = useRouter();
23+
const [search, setSearch] = useState("");
24+
25+
const results = useMemo(
26+
() =>
27+
search === ""
28+
? Details
29+
: Details.filter(
30+
(d) =>
31+
d.main_grammar.includes(search) ||
32+
d.meaning?.actual?.toLowerCase()?.includes(search) ||
33+
d.jlpt_level?.toLowerCase()?.includes(search)
34+
),
35+
[search]
36+
);
2237
return (
23-
<FlatList
24-
data={Details}
25-
getItemLayout={(data, index) => ({
26-
length: 65,
27-
offset: 65 * index,
28-
index,
29-
})}
30-
renderItem={({ item }) => (
31-
<ListItem
32-
hoverTheme
33-
pressTheme
34-
title={item.main_grammar}
35-
subTitle={item.meaning.actual}
36-
icon={BookKey}
37-
iconAfter={<LevelComp level={item.jlpt_level} />}
38-
onPress={() => {
39-
r.push(`/grammar/${item.id}`);
40-
}}
41-
/>
42-
)}
43-
/>
38+
<>
39+
<XStack ai="center" px="$3" gap="$2" py="$2">
40+
<Search />
41+
<Input flex={1} onChangeText={(r) => setSearch(r.toLowerCase())} />
42+
</XStack>
43+
<FlatList
44+
data={results}
45+
getItemLayout={(data, index) => ({
46+
length: 65,
47+
offset: 65 * index,
48+
index,
49+
})}
50+
renderItem={({ item }) => (
51+
<ListItem
52+
hoverTheme
53+
pressTheme
54+
title={item.main_grammar}
55+
subTitle={item.meaning.actual}
56+
icon={BookKey}
57+
iconAfter={<LevelComp level={item.jlpt_level} />}
58+
onPress={() => {
59+
r.push(`/grammar/${item.id}`);
60+
}}
61+
/>
62+
)}
63+
/>
64+
</>
4465
);
4566
}

app/(tabs)/index.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import { useEffect } from "react";
33
import { Heading, Paragraph, YStack } from "tamagui";
44

55
export default function Home() {
6-
const r = useRouter();
7-
useEffect(() => {
8-
setImmediate(() => r.push("/grammar/725"));
9-
}, []);
106
return (
117
<YStack gap="$4" padding="$8">
128
<Heading>Welcome</Heading>

app/(tabs)/settings.tsx

+36-80
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
getAnswersToday,
3+
getQuestionSeenStats,
34
Question,
5+
QuestionAnswer,
46
QuestionWithAnswers,
57
} from "@/services/questions";
68
import React, { useEffect, useMemo } from "react";
@@ -15,14 +17,15 @@ import {
1517
useMedia,
1618
Paragraph,
1719
Button,
20+
ScrollView,
1821
} from "tamagui";
1922
import { answersTodayStore, settingsStore } from "@/services/store";
2023
import { MultipleSelectBox, SelectBox } from "../../components/SelectBox";
21-
import { PieChart } from "@/components/PieChart";
2224
import { useRouter } from "expo-router";
2325
import { Alert } from "react-native";
2426
import { resetAndReseed } from "../../components/SeedProvider";
2527
import { Delete } from "@tamagui/lucide-icons";
28+
import { Statistics } from "@/components/Statistics";
2629

2730
const levelItems: { name: Question["level"] }[] = [
2831
{ name: "N1" },
@@ -88,96 +91,49 @@ const CategoryFilter = () => {
8891
};
8992

9093
const SettingsTab: React.FC = () => {
91-
const theme = useTheme();
92-
const numberOfQuestionsSolvedToday = answersTodayStore((s) => s.data.val);
93-
const [answers, setAnswers] = React.useState<
94-
Awaited<ReturnType<typeof getAnswersToday>>
95-
>([]);
94+
const numberOfAnswersToday = answersTodayStore((s) => s.data.val);
95+
const [answers, setAnswers] = React.useState<QuestionAnswer[]>([]);
9696
useEffect(() => {
9797
getAnswersToday().then((answers) => {
9898
answersTodayStore.getState().update((state) => {
9999
state.val = answers.length;
100100
});
101101
setAnswers(answers);
102102
});
103-
}, [numberOfQuestionsSolvedToday]);
103+
}, [numberOfAnswersToday]);
104104

105-
const correctCount = useMemo(
106-
() =>
107-
answers.filter((s) => s.questions.correctAnswer === s.answers.answer)
108-
.length,
109-
[answers]
110-
);
111-
const incorrectCount = answers.length - correctCount;
112-
const router = useRouter();
105+
const [seenStats, setSeenStats] =
106+
React.useState<Awaited<ReturnType<typeof getQuestionSeenStats> | null>>(
107+
null
108+
);
109+
110+
const { categoryFilter, levelFilter } = settingsStore((state) => state.data);
111+
112+
useEffect(() => {
113+
getQuestionSeenStats({
114+
categoryFilter: categoryFilter.length ? categoryFilter : undefined,
115+
levelFilter: levelFilter.length ? levelFilter : undefined,
116+
}).then((seenStats) => {
117+
setSeenStats(seenStats);
118+
});
119+
}, [numberOfAnswersToday, categoryFilter, levelFilter]);
113120

114121
return (
115-
<YStack padding="$8" gap="$4">
116-
<Heading>Settings</Heading>
117-
<YStack gap="$2">
118-
<CategoryFilter />
119-
<LevelFilter />
120-
</YStack>
121-
<Heading>Statistics</Heading>
122-
<Text>
123-
Number of questions solved today: {numberOfQuestionsSolvedToday}
124-
</Text>
125-
{numberOfQuestionsSolvedToday > 0 && (
126-
<YStack gap="$4">
127-
<XStack gap="$4">
128-
<View w="50%" aspectRatio={1}>
129-
<PieChart
130-
data={[
131-
{
132-
value: correctCount,
133-
color: theme.green11.get(),
134-
},
135-
{
136-
value: incorrectCount,
137-
color: theme.red11.get(),
138-
},
139-
]}
140-
/>
141-
</View>
142-
<YStack gap="$2">
143-
<Paragraph>
144-
Correct: {correctCount} (
145-
{((correctCount / (answers.length || 1)) * 100).toFixed(2)}%)
146-
</Paragraph>
147-
<Paragraph>
148-
Incorrect: {incorrectCount} (
149-
{((incorrectCount / (answers.length || 1)) * 100).toFixed(2)}%)
150-
</Paragraph>
151-
</YStack>
152-
</XStack>
153-
<Button onPress={() => router.push("/review")}>Review</Button>
122+
<ScrollView>
123+
<YStack padding="$8" gap="$4">
124+
<Heading>Settings</Heading>
125+
<YStack gap="$2">
126+
<CategoryFilter />
127+
<LevelFilter />
154128
</YStack>
155-
)}
156-
<Button
157-
marginEnd="$2"
158-
icon={Delete}
159-
variant="outlined"
160-
color="red"
161-
theme="red_active"
162-
size="$2"
163-
onPress={() => {
164-
Alert.alert("Are You Sure?", "This may delete some questions", [
165-
{
166-
text: "Cancel",
167-
style: "cancel",
168-
},
169-
{
170-
text: "Reset",
171-
onPress: () => {
172-
resetAndReseed();
173-
},
174-
},
175-
]);
176-
}}
177-
>
178-
Reset Questions
179-
</Button>
180-
</YStack>
129+
<Heading>Statistics</Heading>
130+
<Statistics
131+
numberOfQuestionsSolvedToday={numberOfAnswersToday}
132+
answers={answers}
133+
seenStats={seenStats}
134+
/>
135+
</YStack>
136+
</ScrollView>
181137
);
182138
};
183139

app/grammar/list.tsx

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { BookKey, ChevronRight } from "@tamagui/lucide-icons";
2+
import { useLocalSearchParams, useRouter } from "expo-router";
3+
import React, { useMemo } from "react";
4+
import { FlatList } from "react-native";
5+
import { ListItem, XStack, Text } from "tamagui";
6+
import Details from "../../scripts/data/grammar_details.json";
7+
8+
const LevelComp = ({ level }: { level: string }) => {
9+
return (
10+
<XStack gap="$2" ai="center">
11+
<XStack px="$2" py="$1" br="$4" bg={"$gray4"}>
12+
<Text color="$gray9">{level}</Text>
13+
</XStack>
14+
<ChevronRight />
15+
</XStack>
16+
);
17+
};
18+
export default function () {
19+
const params = useLocalSearchParams();
20+
const filtered = useMemo(() => {
21+
if (params.grammars && Array.isArray(params.grammars)) {
22+
return Details.filter((d) => params.grammars?.includes(d.main_grammar));
23+
}
24+
return [];
25+
}, [params.grammars]);
26+
const r = useRouter();
27+
return (
28+
<FlatList
29+
data={Details}
30+
getItemLayout={(data, index) => ({
31+
length: 65,
32+
offset: 65 * index,
33+
index,
34+
})}
35+
renderItem={({ item }) => (
36+
<ListItem
37+
hoverTheme
38+
pressTheme
39+
title={item.main_grammar}
40+
subTitle={item.meaning.actual}
41+
icon={BookKey}
42+
iconAfter={<LevelComp level={item.jlpt_level} />}
43+
onPress={() => {
44+
r.push(`/grammar/${item.id}`);
45+
}}
46+
/>
47+
)}
48+
/>
49+
);
50+
}

0 commit comments

Comments
 (0)