From 96299ba9a2f6a8a5a934f6cc4796346f2c962367 Mon Sep 17 00:00:00 2001 From: oneao Date: Mon, 29 Jun 2026 22:21:28 +0800 Subject: [PATCH] 20260629222128 --- code/app/.workbuddy/check_bg.py | 117 ++++++ code/app/.workbuddy/hongbao.webp | Bin 0 -> 4546 bytes code/app/.workbuddy/hongbao_bg_compare.png | Bin 0 -> 33131 bytes code/app/.workbuddy/make_compare.py | 47 +++ code/app/.workbuddy/memory/2026-06-29.md | 22 ++ .../app-rn/src/app/(main)/chat/history.tsx | 4 +- .../app-rn/src/app/(main)/family/settings.tsx | 4 +- .../app/app-rn/src/app/(main)/family/user.tsx | 8 +- .../src/app/clothes/(tabs)/home/index.tsx | 4 +- .../src/app/clothes/(tabs)/manage/index.tsx | 6 +- .../src/app/finance/(tabs)/analysis/index.tsx | 4 +- .../src/app/finance/(tabs)/home/index.tsx | 218 ++++++---- code/app/app-rn/src/app/finance/add/index.tsx | 73 ++-- .../app-rn/src/app/finance/category/index.tsx | 6 +- .../app/app-rn/src/components/BottomSheet.tsx | 373 +++++++++--------- code/app/app-rn/src/components/DateTime.tsx | 60 +-- .../app-rn/src/components/DateTimeRange.tsx | 63 +-- .../finance/FinanceCategoryIcon.tsx | 3 - code/app/app-rn/src/types/finance.ts | 1 + code/app/app-rn/src/uniwind-types.d.ts | 10 +- 20 files changed, 614 insertions(+), 409 deletions(-) create mode 100644 code/app/.workbuddy/check_bg.py create mode 100644 code/app/.workbuddy/hongbao.webp create mode 100644 code/app/.workbuddy/hongbao_bg_compare.png create mode 100644 code/app/.workbuddy/make_compare.py create mode 100644 code/app/.workbuddy/memory/2026-06-29.md diff --git a/code/app/.workbuddy/check_bg.py b/code/app/.workbuddy/check_bg.py new file mode 100644 index 00000000..d518ef16 --- /dev/null +++ b/code/app/.workbuddy/check_bg.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +import sys +import io +import urllib.request +from PIL import Image + +URL = ("http://118.89.70.199:9001/test/finance-category-default/income/hongbao.webp" + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD" + "&X-Amz-Credential=54WPUR104SA9X00EKWS3%2F20260629%2Fus-east-1%2Fs3%2Faws4_request" + "&X-Amz-Date=20260629T105003Z&X-Amz-Expires=86400" + "&X-Amz-Security-Token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3ODI3NzMyMDUsInBhcmVudCI6Im9uZWFvIn0.zkP6L1zg_LPy1UuK-3-VT6LEWuhLNickKWTWDly_PzJCnwIRPS4naSZE9Lx84HtXPVVhxaZguHXl65AXjROJZA" + "&X-Amz-Signature=b763181ad8bf74deb2ac55ac18402d26a91075088c9c109cfd3a4da6cca7b38f" + "&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject") + +out_path = r"D:\workspace\code\app\.workbuddy\hongbao.webp" + +req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"}) +with urllib.request.urlopen(req, timeout=60) as resp: + data = resp.read() +print("Downloaded bytes:", len(data)) +with open(out_path, "wb") as f: + f.write(data) + +img = Image.open(out_path) +print("Format:", img.format) +print("Size:", img.size) +print("Mode:", img.mode) + +# Force load pixel data +img.load() + +has_alpha = img.mode in ("RGBA", "LA") or (img.mode == "P" and "transparency" in img.info) +print("Has alpha channel:", has_alpha) + +if img.mode == "P": + # Convert palette to RGBA to inspect real pixels + rgba = img.convert("RGBA") +elif img.mode != "RGBA": + rgba = img.convert("RGBA") +else: + rgba = img + +w, h = rgba.size +px = rgba.load() + +def info(x, y): + r, g, b, a = px[x, y] + return f"({x},{y}) RGBA=({r},{g},{b},{a})" + +print("\n--- Corner pixels ---") +print("TL", info(0, 0)) +print("TR", info(w - 1, 0)) +print("BL", info(0, h - 1)) +print("BR", info(w - 1, h - 1)) + +print("\n--- Edge midpoints ---") +print("Top-mid", info(w // 2, 0)) +print("Bottom-mid", info(w // 2, h - 1)) +print("Left-mid", info(0, h // 2)) +print("Right-mid", info(w - 1, h // 2)) + +# Statistics over all pixels +total = w * h +transparent = 0 # alpha == 0 +near_transparent = 0 # alpha < 255 and > 0 +fully_opaque = 0 # alpha == 255 +white_pixels = 0 # opaque & RGB ~ (255,255,255) +near_white_bg = 0 # alpha==0 OR rgb near white + +for y in range(h): + for x in range(w): + r, g, b, a = px[x, y] + if a == 0: + transparent += 1 + elif a < 255: + near_transparent += 1 + else: + fully_opaque += 1 + if r > 245 and g > 245 and b > 245: + white_pixels += 1 + +print("\n--- Global statistics ---") +print("Total pixels:", total) +print("Fully transparent (a==0):", transparent, f"({100*transparent/total:.2f}%)") +print("Semi-transparent (0= w - t or y >= h - t + +border_total = 0 +border_trans = 0 +border_white = 0 +for y in range(h): + for x in range(w): + if is_border(x, y, w, h): + border_total += 1 + r, g, b, a = px[x, y] + if a == 0: + border_trans += 1 + elif r > 245 and g > 245 and b > 245: + border_white += 1 + +print("\n--- Border ring (outer 2px) ---") +print("Border pixels:", border_total) +print("Border transparent:", border_trans, f"({100*border_trans/border_total:.2f}%)") +print("Border near-white opaque:", border_white, f"({100*border_white/border_total:.2f}%)") + +print("\n=== CONCLUSION ===") +if border_trans / border_total > 0.5: + print("BACKGROUND IS TRANSPARENT (alpha channel, mostly a==0 at edges)") +elif border_white / border_total > 0.5: + print("BACKGROUND IS WHITE (opaque near-white pixels at edges)") +else: + print("Background is neither clearly transparent nor white - inspect manually") diff --git a/code/app/.workbuddy/hongbao.webp b/code/app/.workbuddy/hongbao.webp new file mode 100644 index 0000000000000000000000000000000000000000..274a50e660721f925722fbc3118be722ec6a3735 GIT binary patch literal 4546 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 literal 0 HcmV?d00001 diff --git a/code/app/.workbuddy/hongbao_bg_compare.png b/code/app/.workbuddy/hongbao_bg_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..4548e7cc80228d6d2f1865eb0f72921be19041ed GIT binary patch literal 33131 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 gzx%O* - - + 筛选聊天记录 应用 - diff --git a/code/app/app-rn/src/app/(main)/family/settings.tsx b/code/app/app-rn/src/app/(main)/family/settings.tsx index 95d50082..028c5835 100644 --- a/code/app/app-rn/src/app/(main)/family/settings.tsx +++ b/code/app/app-rn/src/app/(main)/family/settings.tsx @@ -551,8 +551,7 @@ export default function HomeSettings() { - - + 保存 - diff --git a/code/app/app-rn/src/app/(main)/family/user.tsx b/code/app/app-rn/src/app/(main)/family/user.tsx index 6e3a53cb..48a9367e 100644 --- a/code/app/app-rn/src/app/(main)/family/user.tsx +++ b/code/app/app-rn/src/app/(main)/family/user.tsx @@ -723,8 +723,7 @@ export default function UserSetting() { - - + 保存 - @@ -747,8 +745,7 @@ export default function UserSetting() { - - + 保存 - diff --git a/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx b/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx index b2ee6b54..9c9d091a 100644 --- a/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx +++ b/code/app/app-rn/src/app/clothes/(tabs)/home/index.tsx @@ -485,8 +485,7 @@ export default function ClothingHome() { - - + 筛选衣服 应用 - diff --git a/code/app/app-rn/src/app/clothes/(tabs)/manage/index.tsx b/code/app/app-rn/src/app/clothes/(tabs)/manage/index.tsx index 14faab7a..c1d009f1 100644 --- a/code/app/app-rn/src/app/clothes/(tabs)/manage/index.tsx +++ b/code/app/app-rn/src/app/clothes/(tabs)/manage/index.tsx @@ -659,9 +659,9 @@ export default function ClothingMetadataManage() { > - + {form && ( - + <> {renderFormTitle()} @@ -743,7 +743,7 @@ export default function ClothingMetadataManage() { 保存 - + )} diff --git a/code/app/app-rn/src/app/finance/(tabs)/analysis/index.tsx b/code/app/app-rn/src/app/finance/(tabs)/analysis/index.tsx index e79ab54f..f264c0d0 100644 --- a/code/app/app-rn/src/app/finance/(tabs)/analysis/index.tsx +++ b/code/app/app-rn/src/app/finance/(tabs)/analysis/index.tsx @@ -625,8 +625,7 @@ export default function FinanceAnalysis() { - - + 筛选分析 应用 - diff --git a/code/app/app-rn/src/app/finance/(tabs)/home/index.tsx b/code/app/app-rn/src/app/finance/(tabs)/home/index.tsx index b19bc963..261e6377 100644 --- a/code/app/app-rn/src/app/finance/(tabs)/home/index.tsx +++ b/code/app/app-rn/src/app/finance/(tabs)/home/index.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { Text, TouchableOpacity, View } from "react-native"; -import { Button, Label, Menu, TextField } from "heroui-native"; +import { Image, Modal, Pressable, ScrollView, Text, TouchableOpacity, View } from "react-native"; +import { Button, Label, TextField } from "heroui-native"; import BottomSheet from "@/components/BottomSheet"; import { router, useFocusEffect } from "expo-router"; import { @@ -15,6 +15,7 @@ import { } from "lucide-react-native"; import dayjs from "dayjs"; import LayoutView from "@/components/layout/view"; +import { buildFileUrl } from "@/utils/file"; import EmptyState from "@/components/EmptyStatus"; import DateTimeRange from "@/components/DateTimeRange"; import FinanceCategoryIcon from "@/components/finance/FinanceCategoryIcon"; @@ -46,7 +47,7 @@ const moneyFormatter = new Intl.NumberFormat("zh-CN", { }); function formatMoney(value: number) { - return moneyFormatter.format(value || 0); + return moneyFormatter.format(Math.abs(value)); } function createDefaultFilter(): FilterState { @@ -73,7 +74,6 @@ export default function Home() { const [showDateRange, setShowDateRange] = useState(false); const [bills, setBills] = useState([]); const [activeBill, setActiveBill] = useState(null); - const showActionMenu = activeBill != null; const selectedCategory = useMemo( () => categories.find((item) => item.id === filter.categoryId) ?? null, @@ -167,6 +167,7 @@ export default function Home() { " c.type as category_type,\n" + " c.icon_file_key as category_icon_file_key,\n" + " coalesce(m.nickname, u.name) as member_nickname,\n" + + " u.avatar as member_avatar,\n" + " coalesce(um.nickname, uu.name) as update_member_nickname\n" + "from b_finance f\n" + "left join b_finance_category c on c.id = f.category_id\n" + @@ -287,7 +288,12 @@ export default function Home() { - {filter.startDate} 至 {filter.endDate} + {dayjs(filter.startDate).format("MM.DD")} - {dayjs(filter.endDate).format("MM.DD")} + + + {dayjs(filter.startDate).year() !== dayjs(filter.endDate).year() + ? `${dayjs(filter.startDate).format("YYYY")} / ${dayjs(filter.endDate).format("YYYY")}` + : dayjs(filter.startDate).format("YYYY年")} @@ -314,15 +320,21 @@ export default function Home() { - 支出合计 - - ¥{formatMoney(totals.expense)} + 支出 + + {formatMoney(totals.expense)} - 收入合计 - - ¥{formatMoney(totals.income)} + 收入 + + {formatMoney(totals.income)} + + + + 结余 + = 0 ? "text-emerald-600" : "text-rose-500"}`}> + {formatMoney(totals.income - totals.expense)} @@ -341,8 +353,8 @@ export default function Home() { {dayjs(billSection.title).format("YYYY年MM月DD日")} - -¥{formatMoney(billSection.expenseTotal)} - +¥{formatMoney(billSection.incomeTotal)} + -{formatMoney(billSection.expenseTotal)} + +{formatMoney(billSection.incomeTotal)} @@ -350,47 +362,63 @@ export default function Home() { }, []); const renderItem = useCallback( - ({ item }: { item: FinanceBill }) => { + ({ item, index, section }: { item: FinanceBill; index: number; section: { data: FinanceBill[] } }) => { const isIncome = item.category_type === FinanceType.Income; const isOwnRecord = String(item.create_by ?? "") === String(userId ?? ""); const remark = item.remark?.trim(); + const isFirst = index === 0; + const isLast = index === section.data.length - 1; + const radiusClass = isFirst && isLast + ? "rounded-xl" + : isFirst + ? "rounded-t-xl" + : isLast + ? "rounded-b-xl" + : ""; + + const memberAvatarUri = !isOwnRecord && item.member_avatar + ? buildFileUrl(item.member_avatar) || null + : null; return ( setActiveBill(item)} > - - + + + + + {memberAvatarUri && ( + + )} - - - {item.category_name || "未分类"} - - {!isOwnRecord && !!item.member_nickname && ( - - {item.member_nickname} - - )} - - - {remark || "无备注"} + + {item.category_name || "未分类"} + {!!remark && ( + + {remark} + + )} - {isIncome ? "+" : "-"}¥{formatMoney(Number(item.amount))} + {isIncome ? "+" : "-"}{formatMoney(Number(item.amount))} @@ -456,8 +484,7 @@ export default function Home() { - - + 筛选记账 - + - + setDraftFilter((prev) => ({ ...prev, keyword }))} @@ -494,22 +521,34 @@ export default function Home() { - - - setDraftFilter((prev) => ({ ...prev, categoryId: null }))} - > - - 全部 - - {categories.map(renderCategoryOption)} - + + + {/* 支出 */} + 支出 + + setDraftFilter((prev) => ({ ...prev, categoryId: null }))} + > + + 全部 + + {categories.filter(c => c.type === FinanceType.Expense).map(renderCategoryOption)} + + {/* 收入 */} + 收入 + + {categories.filter(c => c.type === FinanceType.Income).map(renderCategoryOption)} + + @@ -525,7 +564,6 @@ export default function Home() { 应用 - @@ -545,33 +583,51 @@ export default function Home() { }} /> - {showActionMenu && ( - { - if (!open) closeActionMenu(); - }} + {/* 长按操作菜单 */} + + - - - - - - 编辑 - - - - 删除 - - - - 详细 - - - - - )} + e.stopPropagation()} + > + {/* 编辑 */} + + + 编辑 + + {/* 删除 */} + + + 删除 + + {/* 详细 */} + + + 详细 + + + + diff --git a/code/app/app-rn/src/app/finance/add/index.tsx b/code/app/app-rn/src/app/finance/add/index.tsx index 6976c0f5..6ed97e0a 100644 --- a/code/app/app-rn/src/app/finance/add/index.tsx +++ b/code/app/app-rn/src/app/finance/add/index.tsx @@ -39,7 +39,7 @@ const CategoryItem = memo(function CategoryItem({ isActive ? "bg-accent" : "bg-gray-100" }`} > - + { - if (isSheetOpen) { - requestAnimationFrame(() => { - amountInputRef.current?.focus(); - }); - } else { + if (!isSheetOpen) { Keyboard.dismiss(); } }, [isSheetOpen]); @@ -299,59 +295,49 @@ export default function Add() { - {/* 璁拌处 BottomSheet */} - + {/* 记账 BottomSheet */} + amountInputRef.current?.focus()}> - - {/* 选中的分类 */} + {/* 第一行:分类 icon + 名称 | 日期 */} {selectedCategory && ( - - - + + + + + + + {selectedCategory.name} + - - {selectedCategory.name} - + setShowDatePicker(true)}> + + {date} + + + + )} - {/* 金额 */} - - + {/* 金额(居中、放大、无 label) */} + - - - {/* 日期 */} - - - setShowDatePicker(true)}> - - 选择日期 - - {date} - - - - - {/* 备注 */} @@ -375,7 +361,6 @@ export default function Add() { )} - diff --git a/code/app/app-rn/src/app/finance/category/index.tsx b/code/app/app-rn/src/app/finance/category/index.tsx index 8664f49e..fe690591 100644 --- a/code/app/app-rn/src/app/finance/category/index.tsx +++ b/code/app/app-rn/src/app/finance/category/index.tsx @@ -352,9 +352,9 @@ export default function FinanceCategoryList() { > - + {form && ( - + <> 保存 - + )} diff --git a/code/app/app-rn/src/components/BottomSheet.tsx b/code/app/app-rn/src/components/BottomSheet.tsx index 324c928a..0d42d893 100644 --- a/code/app/app-rn/src/components/BottomSheet.tsx +++ b/code/app/app-rn/src/components/BottomSheet.tsx @@ -1,10 +1,3 @@ -import { - BottomSheetBackdrop, - BottomSheetModal, - BottomSheetView, - useBottomSheetTimingConfigs, -} from "@gorhom/bottom-sheet"; -import type { BottomSheetModalProps } from "@gorhom/bottom-sheet"; import { Children, createContext, @@ -14,261 +7,257 @@ import { useContext, useEffect, useRef, + useState, } from "react"; import type { ComponentProps, ReactElement, ReactNode, Ref } from "react"; -import { Keyboard, Text, type TextInput, type TextProps } from "react-native"; -import { Easing } from "react-native-reanimated"; +import { + Animated, + Dimensions, + Keyboard, + Modal, + Pressable, + StyleSheet, + Text, + View, + type TextInput, + type TextProps, + type ViewStyle, +} from "react-native"; import { Input, useBottomSheetAwareHandlers } from "heroui-native"; -type AppBottomSheetProps = Omit< - BottomSheetModalProps, - "backdropComponent" | "animationConfigs" | "onDismiss" -> & { +// ── Props ────────────────────────────────────────────── + +type AppBottomSheetProps = { children: ReactNode; isOpen: boolean; onOpenChange?: (isOpen: boolean) => void; + onOpen?: () => void; backdropOpacity?: number; animationDuration?: number; + backgroundStyle?: ViewStyle; + handleIndicatorStyle?: ViewStyle; }; -type AppBottomSheetContentProps = ComponentProps & { - animateOnMount?: boolean; - animation?: "disabled" | "disable-all" | boolean; - backgroundClassName?: string; +type AppBottomSheetContentProps = { + children: ReactNode; + className?: string; contentContainerClassName?: string; - enableBlurKeyboardOnGesture?: boolean; - keyboardBlurBehavior?: AppBottomSheetProps["keyboardBlurBehavior"]; + animateOnMount?: boolean; + animation?: unknown; + backgroundClassName?: string; + style?: ViewStyle; }; -type AppBottomSheetContextValue = { - isOpen: boolean; - animationDuration: number; -}; +type CtxValue = { isOpen: boolean; animationDuration: number; registerInput: (node: TextInput | null) => void }; +type ContentCtxValue = CtxValue & { shouldAutoFocusSingleInput: boolean }; -type AppBottomSheetContentContextValue = AppBottomSheetContextValue & { - shouldAutoFocusSingleInput: boolean; -}; - -const AppBottomSheetContext = createContext({ - isOpen: false, - animationDuration: 300, +const AppBottomSheetContext = createContext({ isOpen: false, animationDuration: 300, registerInput: () => {} }); +const AppBottomSheetContentContext = createContext({ + isOpen: false, animationDuration: 300, registerInput: () => {}, shouldAutoFocusSingleInput: false, }); -const AppBottomSheetContentContext = createContext({ - isOpen: false, - animationDuration: 300, - shouldAutoFocusSingleInput: false, -}); +// ── Helpers ──────────────────────────────────────────── function setInputRef(ref: Ref, node: TextInput | null) { - if (typeof ref === "function") { - ref(node); - return; - } - - if (ref) { - ref.current = node; - } + if (typeof ref === "function") { ref(node); return; } + if (ref) { ref.current = node; } } - -function isBottomSheetInputElement(node: ReactNode) { +function isSheetInput(node: ReactNode) { if (!isValidElement(node)) return false; - - const type = node.type as { displayName?: string }; - - return ( - typeof type === "object" && - type !== null && - type.displayName === "AppBottomSheetInput" - ); + const t = node.type as { displayName?: string }; + return typeof t === "object" && t?.displayName === "AppBottomSheetInput"; } - -function countBottomSheetInputs(children: ReactNode): number { - let count = 0; - - Children.forEach(children, (child) => { +function countInputs(children: ReactNode): number { + let c = 0; + Children.forEach(children, child => { if (!isValidElement(child)) return; - - if (isBottomSheetInputElement(child)) { - count += 1; - return; - } - - const element = child as ReactElement<{ children?: ReactNode }>; - count += countBottomSheetInputs(element.props.children); + if (isSheetInput(child)) { c += 1; return; } + c += countInputs((child as ReactElement<{ children?: ReactNode }>).props.children); }); - - return count; + return c; } +// ── Root ─────────────────────────────────────────────── + +const SCREEN_H = Dimensions.get("window").height; + function AppBottomSheetRoot({ children, isOpen, onOpenChange, - backdropOpacity = 0.3, - animationDuration = 300, - enablePanDownToClose = true, - // interactive: 键盘弹出时 sheet 上移到键盘上方(gorhom 默认值) - // extend 只是拉到最高 snap point,不上移 → input 仍被键盘遮挡 - keyboardBehavior = "interactive", - keyboardBlurBehavior = "restore", - // adjustPan: 让 gorhom 自己处理键盘避让(gorhom 默认值) - // adjustResize 会在 Android 上导致 interactive 模式被跳过(见 BottomSheet.js 源码) - android_keyboardInputMode = "adjustPan", - enableBlurKeyboardOnGesture = true, + onOpen, + backdropOpacity = 0.35, + animationDuration = 260, backgroundStyle, handleIndicatorStyle, - ...props }: AppBottomSheetProps) { - const bottomSheetModalRef = useRef(null); - const wasPresentedRef = useRef(false); + const slideY = useRef(new Animated.Value(SCREEN_H)).current; + const backdropFade = useRef(new Animated.Value(0)).current; + const keyboardY = useRef(new Animated.Value(0)).current; + const panelY = useRef(Animated.add(slideY, keyboardY)).current; - const animationConfigs = useBottomSheetTimingConfigs({ - duration: animationDuration, - easing: Easing.out(Easing.cubic), - }); + const [visible, setVisible] = useState(false); + const animatingRef = useRef(false); + const focusedInputRef = useRef(null); + const onOpenRef = useRef(onOpen); + onOpenRef.current = onOpen; - const renderBackdrop = useCallback( - (backdropProps: ComponentProps) => ( - - ), - [backdropOpacity], - ); + const doAnimateIn = useCallback(() => { + Animated.parallel([ + Animated.timing(slideY, { toValue: 0, duration: animationDuration, useNativeDriver: true }), + Animated.timing(backdropFade, { toValue: 1, duration: animationDuration, useNativeDriver: true }), + ]).start(() => { + animatingRef.current = false; + onOpenRef.current?.(); + }); + }, [animationDuration]); + + useEffect(() => { + if (!visible) return; + const id = requestAnimationFrame(() => doAnimateIn()); + return () => cancelAnimationFrame(id); + }, [visible]); + + const doAnimateOut = useCallback((done: () => void) => { + Keyboard.dismiss(); + keyboardY.setValue(0); + Animated.parallel([ + Animated.timing(slideY, { toValue: SCREEN_H, duration: animationDuration, useNativeDriver: true }), + Animated.timing(backdropFade, { toValue: 0, duration: animationDuration, useNativeDriver: true }), + ]).start(() => { + animatingRef.current = false; + setVisible(false); + done(); + }); + }, [animationDuration]); useEffect(() => { if (isOpen) { - wasPresentedRef.current = true; - bottomSheetModalRef.current?.present(); - return; - } - - Keyboard.dismiss(); - - if (wasPresentedRef.current) { - bottomSheetModalRef.current?.dismiss(); + animatingRef.current = true; + slideY.setValue(SCREEN_H); + backdropFade.setValue(0); + setVisible(true); + } else if (visible) { + animatingRef.current = true; + doAnimateOut(() => onOpenChange?.(false)); } }, [isOpen]); - const handleDismiss = useCallback(() => { - wasPresentedRef.current = false; - onOpenChange?.(false); - }, [onOpenChange]); + const adjustForKeyboard = useCallback((kbHeight: number) => { + const input = focusedInputRef.current; + if (!input) { + keyboardY.setValue(-kbHeight); + return; + } + setTimeout(() => { + input.measureInWindow((x, y, w, h) => { + const screenBottom = SCREEN_H - kbHeight; + const inputBottom = y + h; + const needed = inputBottom - screenBottom - 16; + keyboardY.setValue(needed > 0 ? -needed : 0); + }); + }, 50); + }, []); + + const registerInput = useCallback((node: TextInput | null) => { + focusedInputRef.current = node; + }, []); + + useEffect(() => { + if (!visible) return; + const s1 = Keyboard.addListener("keyboardWillShow", (e) => adjustForKeyboard(e.endCoordinates.height)); + const s2 = Keyboard.addListener("keyboardDidShow", (e) => adjustForKeyboard(e.endCoordinates.height)); + const h1 = Keyboard.addListener("keyboardWillHide", () => keyboardY.setValue(0)); + const h2 = Keyboard.addListener("keyboardDidHide", () => keyboardY.setValue(0)); + return () => { s1.remove(); s2.remove(); h1.remove(); h2.remove(); }; + }, [visible, adjustForKeyboard]); + + if (!visible) return null; return ( - - + onOpenChange?.(false)} + statusBarTranslucent > - {children} - + onOpenChange?.(false)}> + + + + + + + + + {children} + + + ); } +// ── Portal / Overlay ─────────────────────────────────── + function AppBottomSheetPortal({ children }: { children: ReactNode }) { return <>{children}; } - function AppBottomSheetOverlay() { return null; } -function AppBottomSheetContent({ - children, - className, - contentContainerClassName, - animateOnMount: _animateOnMount, - animation: _animation, - backgroundClassName: _backgroundClassName, - enableBlurKeyboardOnGesture: _enableBlurKeyboardOnGesture, - keyboardBlurBehavior: _keyboardBlurBehavior, - style, - ...props -}: AppBottomSheetContentProps) { - const context = useContext(AppBottomSheetContext); - const inputCount = countBottomSheetInputs(children); +// ── Content ──────────────────────────────────────────── +function AppBottomSheetContent({ + children, className, contentContainerClassName, style, ...props +}: AppBottomSheetContentProps) { + const ctx = useContext(AppBottomSheetContext); + const inputCount = countInputs(children); return ( - - + )} > {children} - + ); } -function AppBottomSheetTitle(props: TextProps) { - return ; -} - -function AppBottomSheetDescription(props: TextProps) { - return ; -} +// ── Input ────────────────────────────────────────────── const AppBottomSheetInput = forwardRef>( ({ onFocus, onBlur, ...props }, ref) => { - // 用 heroui 原生 Input + useBottomSheetAwareHandlers 接入 sheet 键盘状态。 - // hook 内部调用 gorhom 的 useBottomSheetInternal(true)(unsafe 模式), - // 在 sheet context 外返回 no-op handlers,在 sheet 内则把 input 的 focus/blur - // 注册到 animatedKeyboardState,配合 keyboardBehavior="interactive" 触发上移避让。 - // 这样无需用 BottomSheetTextInput,保留 heroui Input 的全部样式与 API。 const handlers = useBottomSheetAwareHandlers(); const inputRef = useRef(null); - const { isOpen, animationDuration, shouldAutoFocusSingleInput } = useContext( - AppBottomSheetContentContext, - ); - const handleRef = useCallback( - (node: TextInput | null) => { - inputRef.current = node; - setInputRef(ref, node); - }, - [ref], - ); - - useEffect(() => { - if (!isOpen || !shouldAutoFocusSingleInput) return; - - const timer = setTimeout(() => { - inputRef.current?.focus(); - }, animationDuration); - - return () => clearTimeout(timer); - }, [animationDuration, isOpen, shouldAutoFocusSingleInput]); + const { registerInput } = useContext(AppBottomSheetContentContext); + const handleRef = useCallback((node: TextInput | null) => { inputRef.current = node; setInputRef(ref, node); }, [ref]); return ( { + onFocus={e => { handlers.onFocus(e); + registerInput(inputRef.current); onFocus?.(e); }} - onBlur={(e) => { + onBlur={e => { handlers.onBlur(e); onBlur?.(e); }} @@ -277,16 +266,18 @@ const AppBottomSheetInput = forwardRef>( ); }, ); - AppBottomSheetInput.displayName = "AppBottomSheetInput"; -const AppBottomSheet = Object.assign(AppBottomSheetRoot, { +function AppBottomSheetTitle(props: TextProps) { return ; } +function AppBottomSheetDescription(props: TextProps) { return ; } + +const BottomSheet = Object.assign(AppBottomSheetRoot, { Portal: AppBottomSheetPortal, Overlay: AppBottomSheetOverlay, Content: AppBottomSheetContent, + Input: AppBottomSheetInput, Title: AppBottomSheetTitle, Description: AppBottomSheetDescription, - Input: AppBottomSheetInput, }); -export default AppBottomSheet; +export default BottomSheet; diff --git a/code/app/app-rn/src/components/DateTime.tsx b/code/app/app-rn/src/components/DateTime.tsx index ce4a96b5..28c04308 100644 --- a/code/app/app-rn/src/components/DateTime.tsx +++ b/code/app/app-rn/src/components/DateTime.tsx @@ -63,8 +63,7 @@ export default function DateTime({ } }, [show, value]); - // 关闭:BottomSheet 的 onOpenChange(false) 或 confirm 后 setOpen(false) - // 立刻通知父组件,不延迟 —— BottomSheet 自身驱动关闭动画 + // 关闭 useEffect(() => { if (!open && show) { onCloseRef.current?.(); @@ -87,35 +86,36 @@ export default function DateTime({ }, [loading, tempDate, finalFormat, onConfirm]); return ( - - - - - - { - if (date) setTempDate(dayjs(date)); - }} - /> + + + + { + if (date) setTempDate(dayjs(date)); + }} + /> - - - - + + + ); } diff --git a/code/app/app-rn/src/components/DateTimeRange.tsx b/code/app/app-rn/src/components/DateTimeRange.tsx index 6683cfdd..78f29209 100644 --- a/code/app/app-rn/src/components/DateTimeRange.tsx +++ b/code/app/app-rn/src/components/DateTimeRange.tsx @@ -2,8 +2,8 @@ import DateTimePicker, { DateType } from "react-native-dates-picker"; import dayjs from "dayjs"; import { Button, Spinner, useThemeColor } from "heroui-native"; import BottomSheet from "@/components/BottomSheet"; -import { View } from "react-native"; import { useState, useEffect, useCallback, useRef } from "react"; +import { View } from "react-native"; import { CheckIcon } from "lucide-react-native"; import { FadeIn } from "react-native-reanimated"; @@ -69,37 +69,38 @@ export default function DateTimeRange({ }, [loading, range, onConfirm]); return ( - - - - - - setRange(params)} - /> + + + + setRange(params)} + /> - - - - + + + ); } diff --git a/code/app/app-rn/src/components/finance/FinanceCategoryIcon.tsx b/code/app/app-rn/src/components/finance/FinanceCategoryIcon.tsx index 0e46d375..473663e5 100644 --- a/code/app/app-rn/src/components/finance/FinanceCategoryIcon.tsx +++ b/code/app/app-rn/src/components/finance/FinanceCategoryIcon.tsx @@ -9,8 +9,6 @@ interface FinanceCategoryIconProps { size?: number; } -const DEFAULT_BG = "#F3F4F6"; - function FinanceCategoryIcon({ fileKey, label, size = 28 }: FinanceCategoryIconProps) { const [failed, setFailed] = useState(false); const uri = useMemo(() => buildFileUrl(fileKey), [fileKey]); @@ -26,7 +24,6 @@ function FinanceCategoryIcon({ fileKey, label, size = 28 }: FinanceCategoryIconP alignItems: "center", justifyContent: "center", overflow: "hidden", - backgroundColor: DEFAULT_BG, }} > {showImage ? ( diff --git a/code/app/app-rn/src/types/finance.ts b/code/app/app-rn/src/types/finance.ts index a5ed7d77..44e2b415 100644 --- a/code/app/app-rn/src/types/finance.ts +++ b/code/app/app-rn/src/types/finance.ts @@ -40,6 +40,7 @@ export interface FinanceBill { category_type: FinanceType; category_icon_file_key: string; member_nickname?: string; + member_avatar?: string; create_time?: string; update_time?: string; update_by?: number; diff --git a/code/app/app-rn/src/uniwind-types.d.ts b/code/app/app-rn/src/uniwind-types.d.ts index 14402dbc..cc099419 100644 --- a/code/app/app-rn/src/uniwind-types.d.ts +++ b/code/app/app-rn/src/uniwind-types.d.ts @@ -1,10 +1,10 @@ // NOTE: This file is generated by uniwind and it should not be edited manually. /// -declare module "uniwind" { - export interface UniwindConfig { - themes: readonly ["light", "dark"]; - } +declare module 'uniwind' { + export interface UniwindConfig { + themes: readonly ['light', 'dark'] + } } -export {}; +export {}