This commit is contained in:
oneao committed 2025-11-13 17:29:44 +08:00
1 parent 68a834dd18
commit a52e73fbba
9 files changed
+255 -141

No files matched your search

+2 -1
View File
@@ -22,7 +22,8 @@
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, },
"edgeToEdgeEnabled": true, "edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false "predictiveBackGestureEnabled": false,
"package": "com.oneao.baseapp"
}, },
"web": { "web": {
"favicon": "./assets/favicon.png" "favicon": "./assets/favicon.png"
+2 -2
View File
@@ -5,8 +5,8 @@
"main": "expo-router/entry", "main": "expo-router/entry",
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
"android": "expo start --android", "android": "expo run:android",
"ios": "expo start --ios", "ios": "expo run:ios",
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
@@ -1,39 +1,68 @@
import React, { useState } from 'react' import React from 'react'
import { KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native' import {
import { KeyboardProvider } from 'react-native-keyboard-controller' StyleSheet,
import { AInput } from '@/components/common/ao' Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native'
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
export default function MyInput() { export default function KeyboardAvoidingViewExample() {
return ( return (
<View style={styles.content}> <KeyboardAvoidingView
<View style={styles.topSpace} /> behavior="padding"
keyboardVerticalOffset={100}
{/* 输入框 */} style={styles.content}
<View style={styles.inputWrapper}> >
<AInput <View style={styles.inner}>
value="" <Text style={styles.heading}>Header</Text>
placeholder="备注" <View>
className="bg-[#f2f3f5] border-0" <TextInput placeholder="Username" style={styles.textInput} />
size="sm" <TextInput placeholder="Password" style={styles.textInput} />
/> </View>
<TouchableOpacity style={styles.button}>
<Text style={styles.text}>Submit</Text>
</TouchableOpacity>
</View> </View>
</View> </KeyboardAvoidingView>
) )
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
flex: 1,
},
content: { content: {
flex: 1,
maxHeight: 600,
},
heading: {
fontSize: 36,
marginBottom: 48,
fontWeight: '600',
},
inner: {
padding: 24,
flex: 1, flex: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
}, },
topSpace: { textInput: {
flex: 1, height: 45,
borderColor: '#000000',
borderWidth: 1,
borderRadius: 10,
marginBottom: 36,
paddingLeft: 10,
}, },
inputWrapper: { button: {
marginHorizontal: 16, marginTop: 12,
marginBottom: 20, height: 45,
borderRadius: 10,
backgroundColor: 'rgb(40, 64, 147)',
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontWeight: '500',
fontSize: 16,
color: 'white',
}, },
}) })
@@ -0,0 +1,50 @@
import type { KeyboardEvent } from 'react-native'
import React, { useEffect, useRef } from 'react'
import { Animated, Keyboard, Platform } from 'react-native'
interface BottomSectionProps {
children: React.ReactNode
safeMargin?: number // 输入框距离键盘的安全距离
}
const BottomSection: React.FC<BottomSectionProps> = ({ children, safeMargin = 8 }) => {
const translateY = useRef(new Animated.Value(0)).current
useEffect(() => {
const onKeyboardShow = (e: KeyboardEvent) => {
const keyboardHeight = e.endCoordinates.height
console.log(keyboardHeight)
Animated.timing(translateY, {
toValue: -(keyboardHeight - safeMargin),
duration: e.duration ?? 250,
useNativeDriver: true,
}).start()
}
const onKeyboardHide = (e: KeyboardEvent) => {
Animated.timing(translateY, {
toValue: 0,
duration: e.duration ?? 250,
useNativeDriver: true,
}).start()
}
const showSub = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
onKeyboardShow,
)
const hideSub = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
onKeyboardHide,
)
return () => {
showSub.remove()
hideSub.remove()
}
}, [safeMargin])
return <Animated.View style={{ transform: [{ translateY }] }}>{children}</Animated.View>
}
export default BottomSection
@@ -1,9 +1,9 @@
import type { MoneyCategoryResult } from '@/request/api/money' import type { MoneyCategoryResult } from '@/request/api/money'
import type { MoneyCategory } from '@/types/money' import type { MoneyCategory } from '@/types/money'
import Big from 'big.js' import Big from 'big.js'
import dayjs from 'dayjs'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { Keyboard, Pressable, StyleSheet, Text, View } from 'react-native' import { Keyboard, Platform, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
import { KeyboardProvider } from 'react-native-keyboard-controller'
import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { AInput, LucideIcon } from '@/components/common/ao' import { AInput, LucideIcon } from '@/components/common/ao'
import SegmentedTabs from '@/components/common/ao/SegmentedTabs' import SegmentedTabs from '@/components/common/ao/SegmentedTabs'
@@ -15,6 +15,7 @@ import {
DrawerHeader, DrawerHeader,
} from '@/components/ui/drawer' } from '@/components/ui/drawer'
import { Grid, GridItem } from '@/components/ui/grid' import { Grid, GridItem } from '@/components/ui/grid'
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
import { getMoneyCategoryApi } from '@/request/api/money' import { getMoneyCategoryApi } from '@/request/api/money'
import MoneyCategoryIcon from './MoneyCategoryIcon' import MoneyCategoryIcon from './MoneyCategoryIcon'
@@ -64,13 +65,13 @@ export default function AddMoneyDrawer({
const [showKeydown, setShowKeydown] = useState(false) const [showKeydown, setShowKeydown] = useState(false)
const [keydownInput, setKeydownInput] = useState<string>('') const [keydownInput, setKeydownInput] = useState<string>('')
const [result, setResult] = useState('0') const [result, setResult] = useState('0')
const [date, setDate] = useState(dayjs())
const [categories, setCategories] = useState<MoneyCategoryResult>({ const [categories, setCategories] = useState<MoneyCategoryResult>({
income: [], income: [],
expense: [], expense: [],
}) })
const keyboardHeight = useKeyboardHeight()
console.log(keyboardHeight)
function selectCategory(item: MoneyCategory) { function selectCategory(item: MoneyCategory) {
setSelectedCategoryId(item.id) setSelectedCategoryId(item.id)
setShowKeydown(true) setShowKeydown(true)
@@ -299,7 +300,6 @@ export default function AddMoneyDrawer({
}, []) }, [])
return ( return (
<Drawer <Drawer
isOpen={isOpen} isOpen={isOpen}
size="full" size="full"
@@ -307,6 +307,7 @@ export default function AddMoneyDrawer({
onClose={onMainDrawerClose} onClose={onMainDrawerClose}
> >
<DrawerBackdrop /> <DrawerBackdrop />
<DrawerContent style={{ padding: 0, flex: 1 }}> <DrawerContent style={{ padding: 0, flex: 1 }}>
<DrawerHeader <DrawerHeader
className="flex items-center justify-between" className="flex items-center justify-between"
@@ -324,7 +325,6 @@ export default function AddMoneyDrawer({
<Text className="text-gray-500 font-medium">取消</Text> <Text className="text-gray-500 font-medium">取消</Text>
</Pressable> </Pressable>
</DrawerHeader> </DrawerHeader>
<DrawerBody style={{ flex: 1, paddingLeft: 16, paddingRight: 16, marginBottom: 0, backgroundColor: '#fff' }}> <DrawerBody style={{ flex: 1, paddingLeft: 16, paddingRight: 16, marginBottom: 0, backgroundColor: '#fff' }}>
<Grid className="gap-4 mb-8" _extra={{ className: 'grid-cols-4' }}> <Grid className="gap-4 mb-8" _extra={{ className: 'grid-cols-4' }}>
{(() => { {(() => {
@@ -366,86 +366,86 @@ export default function AddMoneyDrawer({
</DrawerBody> </DrawerBody>
{showKeydown && ( {showKeydown && (
<View style={[{ <KeyboardProvider>
backgroundColor: '#fff', <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
elevation: 30, <View className="p-4 h-28">
}]} <View className="flex flex-row items-center justify-between">
> <Pressable
<View className="p-4"> className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70"
<View className="flex flex-row items-center justify-between"> >
<Pressable <LucideIcon icon="Calendar" size={16} color="#60a5fa" />
className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70" <Text className="text-gray-600 text-sm">2024/11/05</Text>
> <LucideIcon icon="ChevronDown" size={12} color="#aaa" />
<LucideIcon icon="Calendar" size={16} color="#60a5fa" /> </Pressable>
<Text className="text-gray-600 text-sm">2024/11/05</Text> <Text className="text-3xl text-blue-700 text-right">{result}</Text>
<LucideIcon icon="ChevronDown" size={12} color="#aaa" /> </View>
</Pressable>
<Text className="text-3xl text-blue-700 text-right">{result}</Text> <Text className="text-gray-400 text-right">{keydownInput}</Text>
<AInput
ref={inputRef}
value={keydownInput}
placeholder="备注"
className="bg-[#f2f3f5] border-0"
size="sm"
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
</View> </View>
</TouchableWithoutFeedback>
<View className="flex flex-row flex-wrap" style={{ height: 220 }}>
{keys.map((item, idx) => {
const isFirstCol = idx % 4 === 0
const isFirstRow = idx < 4
const isLastCol = (idx + 1) % 4 === 0
const isLastRow = idx >= 12
<Text className="text-gray-400 text-right">{keydownInput}</Text> return (
<Pressable
key={item.key}
onPress={() => handlePress(item.key)}
style={{
width: '25%',
height: '25%',
justifyContent: 'center',
alignItems: 'center',
borderTopWidth: isFirstRow ? 0.3 : 0,
borderLeftWidth: isFirstCol ? 0.3 : 0,
borderRightWidth: !isLastCol ? 0.3 : 0,
borderBottomWidth: !isLastRow ? 0.3 : 0,
borderColor: '#ddd',
}}
>
{({ pressed }) => {
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
<AInput return (
ref={inputRef} <>
value="" <View
placeholder="备注" style={{
className="bg-[#f2f3f5] border-0" ...StyleSheet.absoluteFillObject,
size="sm" backgroundColor: pressed ? pressedBg : defaultBg,
onFocus={() => setIsFocused(true)} }}
onBlur={() => setIsFocused(false)} />
/> {item.content}
</>
)
}}
</Pressable>
)
})}
</View> </View>
{!isFocused && ( {/* {isFocused && (
<View style={{ height: 240 }}> <View style={{ height:
<View className="flex flex-row flex-wrap h-full"> keyboardHeight + 10 - 220 }}
{keys.map((item, idx) => { >
const isFirstCol = idx % 4 === 0
const isFirstRow = idx < 4
const isLastCol = (idx + 1) % 4 === 0
const isLastRow = idx >= 12
return (
<Pressable
key={item.key}
onPress={() => handlePress(item.key)}
style={{
width: '25%',
height: '25%',
justifyContent: 'center',
alignItems: 'center',
borderTopWidth: isFirstRow ? 0.3 : 0,
borderLeftWidth: isFirstCol ? 0.3 : 0,
borderRightWidth: !isLastCol ? 0.3 : 0,
borderBottomWidth: !isLastRow ? 0.3 : 0,
borderColor: '#ddd',
}}
>
{({ pressed }) => {
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
return (
<>
<View
style={{
...StyleSheet.absoluteFillObject,
backgroundColor: pressed ? pressedBg : defaultBg,
}}
/>
{item.content}
</>
)
}}
</Pressable>
)
})}
</View>
</View> </View>
)} )} */}
</KeyboardProvider>
</View>
)} )}
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
) )
+34 -31
View File
@@ -6,6 +6,7 @@ import * as SplashScreen from 'expo-splash-screen'
import { StatusBar } from 'expo-status-bar' import { StatusBar } from 'expo-status-bar'
import { RefreshCw } from 'lucide-react-native' import { RefreshCw } from 'lucide-react-native'
import { useEffect } from 'react' import { useEffect } from 'react'
import { KeyboardProvider } from 'react-native-keyboard-controller'
import EmptyState from '@/components/common/EmptyStatus' import EmptyState from '@/components/common/EmptyStatus'
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider' import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
@@ -52,39 +53,41 @@ export default function RootLayout() {
}, [fontsLoaded]) }, [fontsLoaded])
return ( return (
<GluestackUIProvider> <KeyboardProvider>
<ToastProvider> <GluestackUIProvider>
<ToastProviderWrapper> <ToastProvider>
<Stack <ToastProviderWrapper>
screenOptions={{ <Stack
headerTitleAlign: 'center', screenOptions={{
animation: 'slide_from_right', headerTitleAlign: 'center',
gestureEnabled: true, animation: 'slide_from_right',
gestureDirection: 'horizontal', gestureEnabled: true,
contentStyle: { gestureDirection: 'horizontal',
backgroundColor: '#fff', contentStyle: {
}, backgroundColor: '#fff',
}} },
>
<Stack.Screen
name="(tabs)"
options={{
headerShown: false,
}} }}
/> >
<Stack.Screen
name="(tabs)"
options={{
headerShown: false,
}}
/>
<Stack.Screen <Stack.Screen
name="auth" name="auth"
options={{ options={{
headerShown: false, headerShown: false,
}} }}
/> />
<Stack.Screen name="+not-found" /> <Stack.Screen name="+not-found" />
</Stack> </Stack>
<StatusBar style="auto" /> <StatusBar style="auto" />
</ToastProviderWrapper> </ToastProviderWrapper>
</ToastProvider> </ToastProvider>
</GluestackUIProvider> </GluestackUIProvider>
</KeyboardProvider>
) )
} }
@@ -55,7 +55,7 @@ const AInput = forwardRef<React.ComponentRef<typeof InputField>, AInputProps>(
)} )}
<InputField <InputField
ref={ref} // 这里直接传递给 InputField ref={ref}
type={isPassword ? (showPassword ? 'text' : 'password') : 'text'} type={isPassword ? (showPassword ? 'text' : 'password') : 'text'}
{...rest} {...rest}
/> />
@@ -0,0 +1,31 @@
import type { KeyboardEvent } from 'react-native'
import { useEffect, useState } from 'react'
import { Keyboard } from 'react-native'
/**
* useKeyboardHeight - 获取键盘高度的 Hooks
* @returns {number} 当前键盘高度,键盘收起时为 0
*/
export function useKeyboardHeight() {
const [keyboardHeight, setKeyboardHeight] = useState(0)
useEffect(() => {
const onKeyboardShow = (e: KeyboardEvent) => {
setKeyboardHeight(e.endCoordinates.height)
}
const onKeyboardHide = () => {
setKeyboardHeight(0)
}
const showSub = Keyboard.addListener('keyboardDidShow', onKeyboardShow)
const hideSub = Keyboard.addListener('keyboardDidHide', onKeyboardHide)
return () => {
showSub.remove()
hideSub.remove()
}
}, [])
return keyboardHeight
}
@@ -30,8 +30,8 @@ function isWhiteListed(url: string) {
} }
const alovaInstance = createAlova({ const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8085/app', // baseURL: 'http://192.168.5.6:8085/app',
// baseURL: 'http://192.168.1.136:8085/app', baseURL: 'http://192.168.1.136:8085/app',
requestAdapter: axiosRequestAdapter(), requestAdapter: axiosRequestAdapter(),
timeout: 5000, timeout: 5000,
cacheFor: null, cacheFor: null,