diff --git a/CHANGELOG.md b/CHANGELOG.md index eb526a08..e4263e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [3.0.135] - 2026-09-15 + +### Bug Fixes +- **native-list (iOS)**: Keep row heights in sync while a wallet group is dragged in the wallet sidebar. UIKit interactive movement lays rows out in the in-flight order, but sizes were still resolved from the pre-drag snapshot, which stretched the neighboring row and clipped the passed group's name (OK-62492). + +### Chores +- Add the "Native List Wallet Sidebar Reorder" example page that replays the OK-62492 wallet sidebar recording. +- Bump all 40 publishable packages to 3.0.135. + ## [3.0.134] - 2026-09-15 ### Bug Fixes diff --git a/example/react-native/pages/NativeListWalletSidebarReorderPage.tsx b/example/react-native/pages/NativeListWalletSidebarReorderPage.tsx new file mode 100644 index 00000000..6c5c1d5c --- /dev/null +++ b/example/react-native/pages/NativeListWalletSidebarReorderPage.tsx @@ -0,0 +1,282 @@ +import { useMemo, useState } from 'react'; +import { + Image, + Pressable, + ScrollView, + StatusBar, + StyleSheet, + Text, + View, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { + NativeList, + type ReorderEvent, + type RowActionEvent, +} from '@onekeyfe/react-native-native-list'; + +import { + OK_62492_FOCUSED_WALLET_ID, + OK_62492_WALLETS, + WALLET_SIDEBAR_LIGHT_THEME, + buildWalletSidebarSnapshot, + parseHiddenWalletActionKey, + reorderWalletSidebarWallets, + walletSidebarAvatarUri, +} from './nativeListWalletSidebarFixture'; + +// app-monorepo AccountSelectorWalletListSideBarV2 uses w="$24" on phones. +const SIDEBAR_WIDTH = 96; +const MAX_EVENT_LINES = 12; + +export function NativeListWalletSidebarReorderPage() { + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + const [wallets, setWallets] = useState(OK_62492_WALLETS); + const [focusedWalletId, setFocusedWalletId] = useState( + OK_62492_FOCUSED_WALLET_ID, + ); + const [listInstance, setListInstance] = useState(0); + const [events, setEvents] = useState([]); + const snapshot = useMemo( + () => buildWalletSidebarSnapshot({ wallets, focusedWalletId }), + [focusedWalletId, wallets], + ); + const focusedWallet = wallets.find(wallet => wallet.id === focusedWalletId); + + const appendEvent = (line: string) => { + console.info('[NativeListWalletSidebarReorder]', line); + setEvents(previous => [line, ...previous].slice(0, MAX_EVENT_LINES)); + }; + + const handleRowAction = (event: RowActionEvent) => { + if (!event.rowKey || event.actionKey !== 'press') return; + const hiddenWalletParentId = parseHiddenWalletActionKey(event.rowKey); + if (hiddenWalletParentId) { + appendEvent(`add hidden wallet · ${hiddenWalletParentId}`); + return; + } + if (wallets.some(wallet => wallet.id === event.rowKey)) { + setFocusedWalletId(event.rowKey); + } + }; + + const handleReorder = (event: ReorderEvent) => { + appendEvent(`reorder ${event.key} → ${event.toIndex}`); + setWallets(previous => reorderWalletSidebarWallets(previous, event)); + }; + + const resetFixture = () => { + setWallets(OK_62492_WALLETS); + setFocusedWalletId(OK_62492_FOCUSED_WALLET_ID); + setEvents([]); + // Remount so native reorder state and scroll offset match the recording again. + setListInstance(instance => instance + 1); + }; + + return ( + + + + + + + + appendEvent('add wallet')} + style={({ pressed }) => [ + styles.createWalletButton, + pressed && styles.pressed, + ]} + > + + + + Wallet + + + + + + + {focusedWallet ? ( + + ) : null} + + {focusedWallet?.name ?? '—'} + + + navigation.goBack()} + style={({ pressed }) => [ + styles.textButton, + pressed && styles.pressed, + ]} + > + Back + + + + OK-62492: long-press “abandon”, drag it slowly up past “然 1s” and + the QR wallet, then back down. + + [ + styles.resetButton, + pressed && styles.pressed, + ]} + > + Reset recording order + + + Order + {wallets.map((wallet, index) => ( + + {`${index + 1}. ${wallet.name}${ + wallet.showAddHiddenWalletButton || + (wallet.hiddenWallets?.length ?? 0) > 0 + ? ' [group]' + : '' + }`} + + ))} + Events + {events.map((line, index) => ( + + {line} + + ))} + + + + + ); +} + +const styles = StyleSheet.create({ + screen: { flex: 1, backgroundColor: '#CFCFCF' }, + sheet: { + flex: 1, + flexDirection: 'row', + backgroundColor: '#FFFFFF', + borderTopLeftRadius: 12, + borderTopRightRadius: 12, + borderCurve: 'continuous', + overflow: 'hidden', + }, + sidebar: { + width: SIDEBAR_WIDTH, + backgroundColor: WALLET_SIDEBAR_LIGHT_THEME.background, + borderRightWidth: StyleSheet.hairlineWidth, + borderRightColor: '#0000000f', + }, + list: { flex: 1 }, + sidebarFooter: { + padding: 8, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: WALLET_SIDEBAR_LIGHT_THEME.separator, + }, + createWallet: { padding: 4, alignItems: 'center' }, + createWalletButton: { + width: 40, + height: 40, + borderRadius: 20, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#202020', + }, + createWalletIcon: { + color: '#FFFFFF', + fontSize: 26, + lineHeight: 28, + }, + createWalletLabel: { + marginTop: 4, + color: '#000000df', + fontSize: 12, + lineHeight: 16, + textAlign: 'center', + }, + details: { flex: 1, backgroundColor: '#FFFFFF' }, + header: { + height: 56, + flexDirection: 'row', + alignItems: 'center', + paddingLeft: 20, + paddingRight: 12, + gap: 8, + }, + headerAvatar: { width: 32, height: 32 }, + headerTitle: { + maxWidth: 140, + color: '#000000df', + fontSize: 16, + fontWeight: '600', + }, + headerSpacer: { flex: 1 }, + textButton: { paddingHorizontal: 8, paddingVertical: 6 }, + textButtonLabel: { color: '#006dcbf2', fontSize: 15 }, + caption: { + marginHorizontal: 20, + color: '#0000009b', + fontSize: 13, + lineHeight: 18, + }, + resetButton: { + alignSelf: 'flex-start', + marginTop: 12, + marginHorizontal: 20, + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 16, + backgroundColor: '#0000000f', + }, + resetButtonLabel: { color: '#000000df', fontSize: 13, fontWeight: '600' }, + log: { flex: 1, marginTop: 12 }, + logContent: { paddingHorizontal: 20, paddingBottom: 24 }, + sectionTitle: { + marginTop: 8, + marginBottom: 4, + color: '#000000df', + fontSize: 13, + fontWeight: '600', + }, + logLine: { + color: '#0000009b', + fontSize: 12, + lineHeight: 17, + fontVariant: ['tabular-nums'], + }, + pressed: { opacity: 0.6 }, +}); diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/bear.png b/example/react-native/pages/nativeListWalletSidebarAssets/bear.png new file mode 100644 index 00000000..138b01cc Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/bear.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/classic.png b/example/react-native/pages/nativeListWalletSidebarAssets/classic.png new file mode 100644 index 00000000..aaf787a4 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/classic.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/cow.png b/example/react-native/pages/nativeListWalletSidebarAssets/cow.png new file mode 100644 index 00000000..890cf412 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/cow.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/dog.png b/example/react-native/pages/nativeListWalletSidebarAssets/dog.png new file mode 100644 index 00000000..26ac5eb7 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/dog.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/fox.png b/example/react-native/pages/nativeListWalletSidebarAssets/fox.png new file mode 100644 index 00000000..956799d1 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/fox.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/frog.png b/example/react-native/pages/nativeListWalletSidebarAssets/frog.png new file mode 100644 index 00000000..53f49d7e Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/frog.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/koala.png b/example/react-native/pages/nativeListWalletSidebarAssets/koala.png new file mode 100644 index 00000000..60535643 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/koala.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/lion.png b/example/react-native/pages/nativeListWalletSidebarAssets/lion.png new file mode 100644 index 00000000..7a45fde0 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/lion.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/monkey.png b/example/react-native/pages/nativeListWalletSidebarAssets/monkey.png new file mode 100644 index 00000000..bfe1c006 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/monkey.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/othersImported.png b/example/react-native/pages/nativeListWalletSidebarAssets/othersImported.png new file mode 100644 index 00000000..c295997d Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/othersImported.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/othersWatching.png b/example/react-native/pages/nativeListWalletSidebarAssets/othersWatching.png new file mode 100644 index 00000000..1eefcae2 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/othersWatching.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/panda.png b/example/react-native/pages/nativeListWalletSidebarAssets/panda.png new file mode 100644 index 00000000..ed426954 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/panda.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/polarBear.png b/example/react-native/pages/nativeListWalletSidebarAssets/polarBear.png new file mode 100644 index 00000000..3149fdee Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/polarBear.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/proBlack.png b/example/react-native/pages/nativeListWalletSidebarAssets/proBlack.png new file mode 100644 index 00000000..3057f8bc Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/proBlack.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarAssets/rabbit.png b/example/react-native/pages/nativeListWalletSidebarAssets/rabbit.png new file mode 100644 index 00000000..6825d680 Binary files /dev/null and b/example/react-native/pages/nativeListWalletSidebarAssets/rabbit.png differ diff --git a/example/react-native/pages/nativeListWalletSidebarFixture.ts b/example/react-native/pages/nativeListWalletSidebarFixture.ts new file mode 100644 index 00000000..ea787426 --- /dev/null +++ b/example/react-native/pages/nativeListWalletSidebarFixture.ts @@ -0,0 +1,315 @@ +import { Image, Platform, type ImageRequireSource } from 'react-native'; +import type { + IdentityRow, + LeadingVisual, + NativeListSnapshot, + NativeListTheme, + ReorderEvent, + RowModel, +} from '@onekeyfe/react-native-native-list'; + +// Wallet sidebar captured from the OK-62492 QA recording (iPhone, iOS, light +// theme). Order, names, avatars, the QR badge, and hidden-wallet groups follow +// the video; names the recording truncates are padded so they truncate at the +// same width. Rows are built like app-monorepo's +// AccountSelectorWalletListSideBarV2, so the example replays the production +// snapshot without a wallet database. + +// Copied from app-monorepo packages/shared/src/assets/wallet/avatar (923d2f36b8). +const avatarSources = { + bear: require('./nativeListWalletSidebarAssets/bear.png'), + classic: require('./nativeListWalletSidebarAssets/classic.png'), + cow: require('./nativeListWalletSidebarAssets/cow.png'), + dog: require('./nativeListWalletSidebarAssets/dog.png'), + fox: require('./nativeListWalletSidebarAssets/fox.png'), + frog: require('./nativeListWalletSidebarAssets/frog.png'), + koala: require('./nativeListWalletSidebarAssets/koala.png'), + lion: require('./nativeListWalletSidebarAssets/lion.png'), + monkey: require('./nativeListWalletSidebarAssets/monkey.png'), + othersImported: require('./nativeListWalletSidebarAssets/othersImported.png'), + othersWatching: require('./nativeListWalletSidebarAssets/othersWatching.png'), + panda: require('./nativeListWalletSidebarAssets/panda.png'), + polarBear: require('./nativeListWalletSidebarAssets/polarBear.png'), + proBlack: require('./nativeListWalletSidebarAssets/proBlack.png'), + rabbit: require('./nativeListWalletSidebarAssets/rabbit.png'), +} satisfies Record; + +export type WalletSidebarAvatar = keyof typeof avatarSources; + +export type WalletSidebarWalletKind = + | 'hd' + | 'hw' + | 'hwHidden' + | 'qr' + | 'imported' + | 'watching'; + +export type WalletSidebarWallet = Readonly<{ + id: string; + name: string; + kind: WalletSidebarWalletKind; + avatar: WalletSidebarAvatar; + // walletListUtils.ts badges QR wallets with "QR". + badge?: string; + hiddenWallets?: readonly WalletSidebarWallet[]; + // Resolved result of shouldShowCreateHiddenWalletSidebarButtonForWallet(). + showAddHiddenWalletButton?: boolean; +}>; + +export type WalletSidebarSnapshotInput = Readonly<{ + wallets: readonly WalletSidebarWallet[]; + focusedWalletId?: string; + theme?: NativeListTheme; +}>; + +export const OK_62492_FOCUSED_WALLET_ID = 'hd-ran'; + +// Order at 00:00. The QA first drags "然" below "Private key", then from 00:13 +// drags the "abandon" group up past "然 1s" and the QR wallet, where "然 1s" +// loses its name and "OneKey Pro" separates from its avatar. +export const OK_62492_WALLETS: readonly WalletSidebarWallet[] = [ + { id: 'hd-research', name: '研发', kind: 'hd', avatar: 'bear' }, + { id: 'hd-ran', name: '然', kind: 'hd', avatar: 'lion' }, + { + id: 'watching', + name: 'Watch-Only', + kind: 'watching', + avatar: 'othersWatching', + }, + { id: 'hd-product', name: '产品', kind: 'hd', avatar: 'rabbit' }, + { id: 'hd-12-words', name: '12 助记词', kind: 'hd', avatar: 'panda' }, + { id: 'hd-wallet-25', name: 'Wallet 25', kind: 'hd', avatar: 'polarBear' }, + { id: 'hd-wallet-12', name: 'Wallet 12', kind: 'hd', avatar: 'fox' }, + { id: 'hd-wallet-10', name: 'Wallet 10', kind: 'hd', avatar: 'fox' }, + { id: 'hd-wallet-18', name: 'Wallet 18', kind: 'hd', avatar: 'monkey' }, + { id: 'hd-wallet-19', name: 'Wallet 19', kind: 'hd', avatar: 'cow' }, + { id: 'hd-wallet-26', name: 'Wallet 26', kind: 'hd', avatar: 'dog' }, + { id: 'hd-haohaohao', name: 'haohaohaohao', kind: 'hd', avatar: 'frog' }, + { id: 'hd-wallet-13', name: 'Wallet 13', kind: 'hd', avatar: 'lion' }, + { id: 'hd-wallet-2', name: 'Wallet 2', kind: 'hd', avatar: 'polarBear' }, + { id: 'hw-qa', name: 'qa', kind: 'hw', avatar: 'proBlack' }, + { + id: 'qr-onekey-pro', + name: 'OneKey Pro QR', + kind: 'qr', + avatar: 'proBlack', + badge: 'QR', + }, + { + id: 'hw-ran-1s', + name: '然 1s', + kind: 'hw', + avatar: 'classic', + showAddHiddenWalletButton: true, + }, + { + id: 'hw-abandon', + name: 'abandon', + kind: 'hw', + avatar: 'proBlack', + showAddHiddenWalletButton: true, + }, + { + id: 'imported', + name: 'Private key', + kind: 'imported', + avatar: 'othersImported', + }, + { id: 'hd-wallet-20', name: 'Wallet 20', kind: 'hd', avatar: 'koala' }, +]; + +// useAccountSelectorNativeListThemeV2(true) resolved with app-monorepo light tokens. +export const WALLET_SIDEBAR_LIGHT_THEME: NativeListTheme = { + background: '#f9f9f9', + rowBackground: '#f9f9f9', + rowSelectedBackground: '#00000017', + rowPressedBackground: '#00000017', + subduedBackground: '#f9f9f9', + strongBackground: '#0000000f', + primaryText: '#000000df', + secondaryText: '#0000009b', + disabledText: '#00000072', + icon: '#0000009b', + iconSubdued: '#00000072', + separator: '#0000001f', + accent: '#000000df', + positive: '#00713fde', + negative: '#c40006d3', + inverseBackground: '#000000df', + inverseText: '#ffffffed', + info: '#006dcbf2', + caution: '#9e6c00', + cautionBackground: '#f4dd0016', +}; + +const WALLET_ROW_HEIGHT = 68; +const HIDDEN_WALLET_TITLE = 'Hidden wallet'; +const ADD_HIDDEN_WALLET_KEY_PREFIX = 'add-hidden:'; + +type WalletOverlay = NonNullable< + Extract['overlays'] +>[number]; + +export function walletSidebarAvatarUri(avatar: WalletSidebarAvatar): string { + const uri = Image.resolveAssetSource(avatarSources[avatar])?.uri ?? ''; + // Release Android builds resolve bundled images to drawable resource names. + if (Platform.OS === 'android' && uri && !uri.includes(':')) { + return `android.resource://com.example/drawable/${uri}`; + } + return uri; +} + +function walletVisual( + wallet: WalletSidebarWallet, + theme: NativeListTheme, + badge: string | number | undefined, +): LeadingVisual { + const overlays: WalletOverlay[] = []; + if (badge !== undefined) { + overlays.push({ + position: 'bottomRight', + height: 16, + offsetX: 1, + offsetY: 2, + text: String(badge), + tintColor: theme.primaryText, + backgroundColor: theme.subduedBackground, + }); + } + if (wallet.kind === 'hwHidden') { + return { + kind: 'wallet', + backgroundColor: '#00000000', + fallbackIcon: { name: 'LockSolid', tintColor: theme.icon }, + overlays, + }; + } + return { + kind: 'wallet', + shape: 'square', + backgroundColor: '#00000000', + image: { + uri: walletSidebarAvatarUri(wallet.avatar), + width: 40, + height: 40, + contentFit: 'cover', + retryTimes: 1, + }, + fallbackText: '', + overlays, + }; +} + +function walletIdentityRow( + wallet: WalletSidebarWallet, + focusedWalletId: string | undefined, + theme: NativeListTheme, + badge?: number, +): IdentityRow { + return { + type: 'identity', + key: wallet.id, + testID: `wallet-${wallet.id}`, + presentation: 'walletSidebar', + height: WALLET_ROW_HEIGHT, + title: wallet.name, + leading: walletVisual(wallet, theme, badge ?? wallet.badge), + selected: focusedWalletId === wallet.id, + opacity: 1, + badges: [], + draggable: true, + accessibilityLabel: wallet.name, + }; +} + +function addHiddenWalletRow( + wallet: WalletSidebarWallet, + theme: NativeListTheme, +): IdentityRow { + return { + type: 'identity', + key: `${ADD_HIDDEN_WALLET_KEY_PREFIX}${wallet.id}`, + presentation: 'walletSidebar', + height: WALLET_ROW_HEIGHT, + title: HIDDEN_WALLET_TITLE, + leading: { + kind: 'wallet', + backgroundColor: '#00000000', + shape: 'circle', + borderStyle: 'dashed', + borderColor: theme.separator, + fallbackIcon: { name: 'PlusSmallOutline', tintColor: theme.iconSubdued }, + }, + draggable: false, + }; +} + +export function buildWalletSidebarRows({ + wallets, + focusedWalletId, + theme = WALLET_SIDEBAR_LIGHT_THEME, +}: WalletSidebarSnapshotInput): RowModel[] { + return wallets.map((wallet): RowModel => { + const parent = walletIdentityRow(wallet, focusedWalletId, theme); + const isHwOrQrWallet = wallet.kind === 'hw' || wallet.kind === 'qr'; + const childWallets = isHwOrQrWallet ? wallet.hiddenWallets ?? [] : []; + const hasGroup = + wallet.kind !== 'hwHidden' && + (childWallets.length > 0 || wallet.showAddHiddenWalletButton === true); + if (!hasGroup) return parent; + // Phones use the md layout, which numbers hidden wallets inside a device group. + const children = childWallets.map((child, index) => + walletIdentityRow(child, focusedWalletId, theme, index + 1), + ); + if (wallet.showAddHiddenWalletButton) { + children.push(addHiddenWalletRow(wallet, theme)); + } + return { + type: 'walletGroup', + key: wallet.id, + parent, + children, + draggable: true, + }; + }); +} + +export function buildWalletSidebarSnapshot( + input: WalletSidebarSnapshotInput, +): NativeListSnapshot { + return { + schemaVersion: 1, + generation: 1, + theme: input.theme ?? WALLET_SIDEBAR_LIGHT_THEME, + layout: { + kind: 'linear', + contentPaddingHorizontal: 8, + contentPaddingTop: 8, + contentPaddingBottom: 8, + itemSpacing: 12, + }, + rows: buildWalletSidebarRows(input), + capabilities: { reorderable: true }, + }; +} + +export function parseHiddenWalletActionKey(rowKey: string): string | undefined { + return rowKey.startsWith(ADD_HIDDEN_WALLET_KEY_PREFIX) + ? rowKey.slice(ADD_HIDDEN_WALLET_KEY_PREFIX.length) + : undefined; +} + +// Same move as the production sidebar's onReorder handler. +export function reorderWalletSidebarWallets( + wallets: readonly WalletSidebarWallet[], + event: ReorderEvent, +): readonly WalletSidebarWallet[] { + const fromIndex = wallets.findIndex(wallet => wallet.id === event.key); + if (fromIndex < 0) return wallets; + const reordered = [...wallets]; + const [moved] = reordered.splice(fromIndex, 1); + if (!moved) return wallets; + const toIndex = Math.max(0, Math.min(event.toIndex, reordered.length)); + reordered.splice(toIndex, 0, moved); + return reordered; +} diff --git a/example/react-native/route.tsx b/example/react-native/route.tsx index 3c7ba20c..d1a0b8f3 100644 --- a/example/react-native/route.tsx +++ b/example/react-native/route.tsx @@ -29,6 +29,7 @@ import { NativeListAccountSelectorPage } from './pages/NativeListAccountSelector import type { AccountSelectorInitialTargetInput } from './pages/nativeListAccountSelectorData'; import { NativeListNetworkSelectorPage } from './pages/NativeListNetworkSelectorPage'; import { NativeListTokenSelectorPage } from './pages/NativeListTokenSelectorPage'; +import { NativeListWalletSidebarReorderPage } from './pages/NativeListWalletSidebarReorderPage'; import type { MarketSearchParams } from './pages/MarketNativePagerExamplePage'; import { NativeListExamplePage, @@ -85,6 +86,7 @@ export type RootStackParamList = { NativeListAccountSelector: AccountSelectorInitialTargetInput | undefined; NativeListNetworkSelector: undefined; NativeListTokenSelector: undefined; + NativeListWalletSidebarReorder: undefined; MarketNativePager: undefined; MarketSearch: MarketSearchParams; OtaPipeline: undefined; @@ -280,6 +282,13 @@ const modules: { 'Production network catalog with native sections, search, and selection', icon: '🌐', }, + { + screen: 'NativeListWalletSidebarReorder', + name: 'Native List Wallet Sidebar Reorder', + description: + 'OK-62492 recorded wallet sidebar for iOS wallet group drag reorder', + icon: '🗂️', + }, { screen: 'PagerView', name: 'Pager View', @@ -671,6 +680,12 @@ export function AppNavigator() { component={NativeListTokenSelectorPage} options={{ headerShown: false }} /> + NativeListItem? { + guard let keys = interactiveMovementKeys else { return item(at: indexPath) } + return keys[safe: indexPath.item].flatMap { itemsByKey[$0] } + } + + private func updateInteractiveMovementKeys(movingFrom source: IndexPath, to target: IndexPath) { + guard interactiveMovementKeys != nil else { return } + var keys = dataSource.snapshot().itemIdentifiers + guard keys.indices.contains(source.item) else { return } + let key = keys.remove(at: source.item) + keys.insert(key, at: min(max(target.item, 0), keys.count)) + interactiveMovementKeys = keys + } + private func item(at indexPath: IndexPath) -> NativeListItem? { if let key = dataSource.itemIdentifier(for: indexPath), let item = itemsByKey[key] { return item @@ -2049,7 +2073,9 @@ extension NativeListView: UICollectionViewDelegateFlowLayout { atCurrentIndexPath currentIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath ) -> IndexPath { - interactiveReorderUsesAtomicTargeting ? currentIndexPath : proposedIndexPath + guard !interactiveReorderUsesAtomicTargeting else { return currentIndexPath } + updateInteractiveMovementKeys(movingFrom: originalIndexPath, to: proposedIndexPath) + return proposedIndexPath } func collectionView( @@ -2057,7 +2083,9 @@ extension NativeListView: UICollectionViewDelegateFlowLayout { targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath ) -> IndexPath { - interactiveReorderUsesAtomicTargeting ? originalIndexPath : proposedIndexPath + guard !interactiveReorderUsesAtomicTargeting else { return originalIndexPath } + updateInteractiveMovementKeys(movingFrom: originalIndexPath, to: proposedIndexPath) + return proposedIndexPath } func collectionView( @@ -2065,7 +2093,7 @@ extension NativeListView: UICollectionViewDelegateFlowLayout { layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath ) -> CGSize { - guard let config, let item = item(at: indexPath) else { return .zero } + guard let config, let item = layoutItem(at: indexPath) else { return .zero } let insets = flowLayout.sectionInset if config.orientation == "horizontal" { let width: CGFloat = item.type == "rail" ? railWidth(item) : item.type == "mediaTile" ? 200 : 280 diff --git a/native-views/react-native-native-list/package.json b/native-views/react-native-native-list/package.json index e1e9df4e..643372bd 100644 --- a/native-views/react-native-native-list/package.json +++ b/native-views/react-native-native-list/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-native-list", - "version": "3.0.134", + "version": "3.0.135", "description": "Template-driven native RecyclerView and UICollectionView for React Native", "source": "./src/index.ts", "main": "./lib/module/index.js", @@ -83,8 +83,8 @@ "typescript": "^5.9.2" }, "peerDependencies": { - "@onekeyfe/react-native-image": "3.0.134", - "@onekeyfe/react-native-native-logger": "3.0.134", + "@onekeyfe/react-native-image": "3.0.135", + "@onekeyfe/react-native-native-logger": "3.0.135", "react": "*", "react-native": "*", "react-native-nitro-modules": "0.37.0" diff --git a/native-views/react-native-native-sheet/package.json b/native-views/react-native-native-sheet/package.json index d2a4f688..5364a302 100644 --- a/native-views/react-native-native-sheet/package.json +++ b/native-views/react-native-native-sheet/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-native-sheet", - "version": "3.0.134", + "version": "3.0.135", "description": "Native bottom sheet host for arbitrary React Native content", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-pager-view/package.json b/native-views/react-native-pager-view/package.json index ad423331..a3b90120 100644 --- a/native-views/react-native-pager-view/package.json +++ b/native-views/react-native-pager-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pager-view", - "version": "3.0.134", + "version": "3.0.135", "description": "React Native wrapper for Android and iOS ViewPager", "source": "./src/index.tsx", "main": "./lib/module/index.js", @@ -66,7 +66,7 @@ "typescript": "^5.9.2" }, "peerDependencies": { - "@onekeyfe/react-native-native-logger": "3.0.134", + "@onekeyfe/react-native-native-logger": "3.0.135", "react": "*", "react-native": "*" }, diff --git a/native-views/react-native-perp-depth-bar/package.json b/native-views/react-native-perp-depth-bar/package.json index 5d546967..91c225a7 100644 --- a/native-views/react-native-perp-depth-bar/package.json +++ b/native-views/react-native-perp-depth-bar/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perp-depth-bar", - "version": "3.0.134", + "version": "3.0.135", "description": "react-native-perp-depth-bar", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-scroll-guard/package.json b/native-views/react-native-scroll-guard/package.json index 1994bf76..929a9b24 100644 --- a/native-views/react-native-scroll-guard/package.json +++ b/native-views/react-native-scroll-guard/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-scroll-guard", - "version": "3.0.134", + "version": "3.0.135", "description": "A native view wrapper that prevents parent scrollable containers (PagerView/ViewPager2) from intercepting child scroll gestures", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-segment-slider/package.json b/native-views/react-native-segment-slider/package.json index 8bd333d1..6cc67f5c 100644 --- a/native-views/react-native-segment-slider/package.json +++ b/native-views/react-native-segment-slider/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-segment-slider", - "version": "3.0.134", + "version": "3.0.135", "description": "react-native-segment-slider", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-skeleton/package.json b/native-views/react-native-skeleton/package.json index 8a53fb46..8b88f5fb 100644 --- a/native-views/react-native-skeleton/package.json +++ b/native-views/react-native-skeleton/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-skeleton", - "version": "3.0.134", + "version": "3.0.135", "description": "react-native-skeleton", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-tab-view/package.json b/native-views/react-native-tab-view/package.json index fbac749f..16f00363 100644 --- a/native-views/react-native-tab-view/package.json +++ b/native-views/react-native-tab-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tab-view", - "version": "3.0.134", + "version": "3.0.135", "description": "Native Bottom Tabs for React Native (UIKit implementation)", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-text-input/package.json b/native-views/react-native-text-input/package.json index 6503a287..8f5cf912 100644 --- a/native-views/react-native-text-input/package.json +++ b/native-views/react-native-text-input/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-text-input", - "version": "3.0.134", + "version": "3.0.135", "description": "React Native TextInput with native paste events", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-text/package.json b/native-views/react-native-text/package.json index 80c52b81..f9146238 100644 --- a/native-views/react-native-text/package.json +++ b/native-views/react-native-text/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-text", - "version": "3.0.134", + "version": "3.0.135", "description": "Opt-in native text rendering for React Native", "source": "./src/index.ts", "main": "./lib/module/index.js", diff --git a/yarn.lock b/yarn.lock index 2b7af108..8f1e2c8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3550,7 +3550,7 @@ __metadata: react-test-renderer: "npm:19.2.3" typescript: "npm:^5.9.2" peerDependencies: - "@onekeyfe/react-native-skeleton": 3.0.134 + "@onekeyfe/react-native-skeleton": 3.0.135 react: "*" react-native: "*" react-native-nitro-modules: 0.37.0 @@ -3651,8 +3651,8 @@ __metadata: react-native-nitro-modules: "npm:0.37.0" typescript: "npm:^5.9.2" peerDependencies: - "@onekeyfe/react-native-image": 3.0.134 - "@onekeyfe/react-native-native-logger": 3.0.134 + "@onekeyfe/react-native-image": 3.0.135 + "@onekeyfe/react-native-native-logger": 3.0.135 react: "*" react-native: "*" react-native-nitro-modules: 0.37.0 @@ -3804,7 +3804,7 @@ __metadata: react-native-builder-bob: "npm:^0.40.13" typescript: "npm:^5.9.2" peerDependencies: - "@onekeyfe/react-native-native-logger": 3.0.134 + "@onekeyfe/react-native-native-logger": 3.0.135 react: "*" react-native: "*" languageName: unknown