|
|
@ -6,7 +6,7 @@ import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'; |
|
|
|
import '@/global.css'; |
|
|
|
import '@/global.css'; |
|
|
|
import { useWordbook, WordbookProvider } from './lib/wordbook/context'; |
|
|
|
import { useWordbook, WordbookProvider } from './lib/wordbook/context'; |
|
|
|
import { Text } from './components/ui/text'; |
|
|
|
import { Text } from './components/ui/text'; |
|
|
|
import { useMemo, useState } from 'react'; |
|
|
|
import { useMemo, useRef, useState } from 'react'; |
|
|
|
import { jaPack, languages } from './lib/providers/dictionaries'; |
|
|
|
import { jaPack, languages } from './lib/providers/dictionaries'; |
|
|
|
import { Input, InputField } from './components/ui/input'; |
|
|
|
import { Input, InputField } from './components/ui/input'; |
|
|
|
import { HStack } from './components/ui/hstack'; |
|
|
|
import { HStack } from './components/ui/hstack'; |
|
|
@ -17,7 +17,8 @@ import { Toast, ToastDescription, ToastTitle, useToast } from './components/ui/t |
|
|
|
|
|
|
|
|
|
|
|
import mobileAds from 'react-native-google-mobile-ads'; |
|
|
|
import mobileAds from 'react-native-google-mobile-ads'; |
|
|
|
import { Icon } from './components/ui/icon'; |
|
|
|
import { Icon } from './components/ui/icon'; |
|
|
|
import { Bookmark, BookMarked, BookOpen, Search } from 'lucide-react-native'; |
|
|
|
import { Bookmark, BookmarkCheck, BookMarked, BookOpen, Search } from 'lucide-react-native'; |
|
|
|
|
|
|
|
import * as Animatable from 'react-native-animatable'; |
|
|
|
|
|
|
|
|
|
|
|
function MainScreen() { |
|
|
|
function MainScreen() { |
|
|
|
const [q, setQ] = useState(''); |
|
|
|
const [q, setQ] = useState(''); |
|
|
@ -36,6 +37,11 @@ function MainScreen() { |
|
|
|
|
|
|
|
|
|
|
|
const { add, items, remove, clear, loading } = useWordbook(); |
|
|
|
const { add, items, remove, clear, loading } = useWordbook(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const baseText = (query || q).trim(); |
|
|
|
|
|
|
|
const isSaved = |
|
|
|
|
|
|
|
!!baseText && |
|
|
|
|
|
|
|
items.some((it) => it.text.toLowerCase() === baseText.toLowerCase() && it.lang === pack.code); |
|
|
|
|
|
|
|
|
|
|
|
const urls = useMemo( |
|
|
|
const urls = useMemo( |
|
|
|
() => |
|
|
|
() => |
|
|
|
providers.reduce<Record<string, string>>((acc, p) => { |
|
|
|
providers.reduce<Record<string, string>>((acc, p) => { |
|
|
@ -55,23 +61,28 @@ function MainScreen() { |
|
|
|
const onSave = () => { |
|
|
|
const onSave = () => { |
|
|
|
if (!canSearch) return; |
|
|
|
if (!canSearch) return; |
|
|
|
Keyboard.dismiss(); |
|
|
|
Keyboard.dismiss(); |
|
|
|
add(q.trim(), pack.code); |
|
|
|
|
|
|
|
|
|
|
|
const toastDesc = isSaved ? '이미 단어장에 있어요' : '단어장에 저장되었습니다'; |
|
|
|
|
|
|
|
if (!isSaved) add(q.trim(), pack.code); |
|
|
|
|
|
|
|
|
|
|
|
toast.show({ |
|
|
|
toast.show({ |
|
|
|
id: 'onSave', |
|
|
|
|
|
|
|
placement: 'top', |
|
|
|
placement: 'top', |
|
|
|
duration: 2000, |
|
|
|
duration: 2000, |
|
|
|
render: () => ( |
|
|
|
render: ({ id }) => { |
|
|
|
<View style={{ marginTop: insets.top + 12, alignItems: 'center', width: '100%' }}> |
|
|
|
console.log('toast render called'); |
|
|
|
<Toast |
|
|
|
return ( |
|
|
|
className="rounded-full opacity-80 mx-5 py-2 flex-row items-center gap-2 shadow-lg max-w-[90%]" |
|
|
|
<View style={{ marginTop: insets.top + 12, alignItems: 'center', width: '100%' }}> |
|
|
|
variant="solid" |
|
|
|
<Toast |
|
|
|
> |
|
|
|
nativeID={`toast-${id}`} |
|
|
|
<ToastTitle>✅</ToastTitle> |
|
|
|
variant="solid" |
|
|
|
<ToastDescription>단어장에 저장되었습니다</ToastDescription> |
|
|
|
className="rounded-full opacity-90 mx-5 px-5 py-2 flex-row items-center gap-2 shadow-lg max-w-[90%]" |
|
|
|
</Toast> |
|
|
|
> |
|
|
|
</View> |
|
|
|
<ToastTitle>✅</ToastTitle> |
|
|
|
), |
|
|
|
<ToastDescription>{toastDesc}</ToastDescription> |
|
|
|
|
|
|
|
</Toast> |
|
|
|
|
|
|
|
</View> |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -108,16 +119,19 @@ function MainScreen() { |
|
|
|
onSubmitEditing={onSearch} |
|
|
|
onSubmitEditing={onSearch} |
|
|
|
/> |
|
|
|
/> |
|
|
|
</Input> |
|
|
|
</Input> |
|
|
|
<Button |
|
|
|
<Animatable.View animation="tada" duration={600} useNativeDriver> |
|
|
|
className="p-1" |
|
|
|
<Button |
|
|
|
onPress={onSave} |
|
|
|
className="p-1" |
|
|
|
variant="outline" |
|
|
|
onPress={onSave} |
|
|
|
action="secondary" |
|
|
|
variant="outline" |
|
|
|
isDisabled={!canSearch} |
|
|
|
action="secondary" |
|
|
|
accessibilityLabel="단어장 저장" |
|
|
|
isDisabled={!canSearch} |
|
|
|
> |
|
|
|
accessibilityLabel="단어장 저장" |
|
|
|
<Icon as={Bookmark} /> |
|
|
|
> |
|
|
|
</Button> |
|
|
|
<Icon as={isSaved ? BookmarkCheck : Bookmark} /> |
|
|
|
|
|
|
|
</Button> |
|
|
|
|
|
|
|
</Animatable.View> |
|
|
|
|
|
|
|
|
|
|
|
<Button onPress={onSearch} isDisabled={!canSearch}> |
|
|
|
<Button onPress={onSearch} isDisabled={!canSearch}> |
|
|
|
<ButtonText>검색</ButtonText> |
|
|
|
<ButtonText>검색</ButtonText> |
|
|
|
</Button> |
|
|
|
</Button> |
|
|
@ -228,6 +242,7 @@ export default function App() { |
|
|
|
<StatusBar style="auto" /> |
|
|
|
<StatusBar style="auto" /> |
|
|
|
<WordbookProvider> |
|
|
|
<WordbookProvider> |
|
|
|
<MainScreen /> |
|
|
|
<MainScreen /> |
|
|
|
|
|
|
|
<Toast /> |
|
|
|
</WordbookProvider> |
|
|
|
</WordbookProvider> |
|
|
|
</SafeAreaView> |
|
|
|
</SafeAreaView> |
|
|
|
</SafeAreaProvider> |
|
|
|
</SafeAreaProvider> |
|
|
|