From c2bef1b382b6892bf40ff085119e969f63140061 Mon Sep 17 00:00:00 2001 From: Arda Erturk Date: Fri, 6 Jun 2025 16:42:15 -0400 Subject: [PATCH 1/2] Implement iOS style home screen --- README.md | 6 +- src/components/apps/AppIcon.tsx | 208 ---------------- src/components/apps/FolderIcon.tsx | 235 ------------------ src/components/apps/IOSDragDropAppsScreen.tsx | 234 +++++++++++------ src/components/apps/SearchWidget.tsx | 131 ---------- 5 files changed, 157 insertions(+), 657 deletions(-) delete mode 100644 src/components/apps/AppIcon.tsx delete mode 100644 src/components/apps/FolderIcon.tsx delete mode 100644 src/components/apps/SearchWidget.tsx diff --git a/README.md b/README.md index 1cc6654..0b5fcba 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ Interspace is a wallet wrapper that allows users to: - Link existing wallets (MetaMask, Coinbase, etc.) - Grant ERC-20 token allowances for seamless transactions -### 2. Apps -- iPhone-style home screen with bookmarked Web3 apps -- Drag-and-drop app organization with folders +- iOS inspired home screen with dynamic app icons and folders +- Horizontal paging with page dots just like iOS +- Drag-and-drop organization and folder creation - Built-in browser with wallet injection - Custom transaction confirmation UI diff --git a/src/components/apps/AppIcon.tsx b/src/components/apps/AppIcon.tsx deleted file mode 100644 index 0d09b4c..0000000 --- a/src/components/apps/AppIcon.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import React, { useEffect } from 'react'; -import { - StyleSheet, - Text, - View, - TouchableOpacity, - Image, - Pressable, -} from 'react-native'; -import Animated, { - useAnimatedStyle, - useSharedValue, - withRepeat, - withSequence, - withTiming, - interpolate, - runOnJS, -} from 'react-native-reanimated'; -import { Apple } from '@/constants/AppleDesign'; - -interface AppIconProps { - id: string; - name: string; - url: string; - iconUrl?: string; - isEditMode: boolean; - onPress: () => void; - onLongPress: () => void; - onDelete: () => void; -} - -const AnimatedPressable = Animated.createAnimatedComponent(Pressable); - -export function AppIcon({ - id, - name, - url, - iconUrl, - isEditMode, - onPress, - onLongPress, - onDelete, -}: AppIconProps) { - const rotation = useSharedValue(0); - const scale = useSharedValue(1); - - // Jiggly animation for edit mode - useEffect(() => { - if (isEditMode) { - rotation.value = withRepeat( - withSequence( - withTiming(-2, { duration: 100 }), - withTiming(2, { duration: 100 }), - withTiming(-2, { duration: 100 }), - withTiming(2, { duration: 100 }) - ), - -1, - true - ); - } else { - rotation.value = withTiming(0, { duration: 100 }); - } - }, [isEditMode]); - - const animatedStyle = useAnimatedStyle(() => { - return { - transform: [ - { rotate: `${rotation.value}deg` }, - { scale: scale.value }, - ], - }; - }); - - const handlePressIn = () => { - scale.value = withTiming(0.95, { duration: 100 }); - }; - - const handlePressOut = () => { - scale.value = withTiming(1, { duration: 100 }); - }; - - const getAppInitial = () => { - return name.charAt(0).toUpperCase(); - }; - - const getAppColor = () => { - // Generate consistent color from app name - const colors = [ - '#FF453A', '#FF9F0A', '#FFD60A', '#30D158', - '#40C8E0', '#007AFF', '#5856D6', '#AF52DE', - '#FF2D92', - ]; - const index = name.charCodeAt(0) % colors.length; - return colors[index]; - }; - - return ( - - - {/* App Icon */} - - {iconUrl ? ( - - ) : ( - - {getAppInitial()} - - )} - - - {/* Delete button */} - {isEditMode && ( - - - × - - - )} - - - {/* App Name */} - - {name} - - - ); -} - -const styles = StyleSheet.create({ - container: { - width: 74, - height: 90, - alignItems: 'center', - }, - iconWrapper: { - position: 'relative', - }, - iconContainer: { - width: 60, - height: 60, - borderRadius: Apple.Radius.medium, - overflow: 'hidden', - backgroundColor: Apple.Colors.tertiarySystemBackground, - }, - iconImage: { - width: '100%', - height: '100%', - }, - iconPlaceholder: { - width: '100%', - height: '100%', - justifyContent: 'center', - alignItems: 'center', - }, - iconInitial: { - fontSize: 24, - fontWeight: '600', - color: 'white', - }, - appName: { - marginTop: 6, - fontSize: Apple.Typography.caption1.fontSize, - fontWeight: Apple.Typography.caption1.fontWeight, - lineHeight: Apple.Typography.caption1.lineHeight, - color: Apple.Colors.label, - textAlign: 'center', - width: '100%', - paddingHorizontal: 2, - }, - deleteButton: { - position: 'absolute', - top: -8, - left: -8, - width: 24, - height: 24, - borderRadius: 12, - backgroundColor: Apple.Colors.systemRed, - justifyContent: 'center', - alignItems: 'center', - ...Apple.Shadows.level2, - }, - deleteButtonInner: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: Apple.Colors.systemRed, - justifyContent: 'center', - alignItems: 'center', - }, - deleteButtonText: { - color: 'white', - fontSize: 18, - fontWeight: '300', - lineHeight: 18, - marginTop: -2, - }, -}); diff --git a/src/components/apps/FolderIcon.tsx b/src/components/apps/FolderIcon.tsx deleted file mode 100644 index 2a48662..0000000 --- a/src/components/apps/FolderIcon.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import React, { useEffect } from 'react'; -import { - StyleSheet, - Text, - View, - TouchableOpacity, - Pressable, -} from 'react-native'; -import Animated, { - useAnimatedStyle, - useSharedValue, - withRepeat, - withSequence, - withTiming, - withSpring, -} from 'react-native-reanimated'; -import { Apple } from '@/constants/AppleDesign'; - -interface App { - id: string; - name: string; - url: string; - iconUrl?: string; -} - -interface FolderIconProps { - id: string; - name: string; - apps: App[]; - color?: string; - isEditMode: boolean; - onPress: () => void; - onLongPress: () => void; - onDelete: () => void; -} - -const AnimatedPressable = Animated.createAnimatedComponent(Pressable); - -export function FolderIcon({ - id, - name, - apps, - color = Apple.Colors.systemGray4, - isEditMode, - onPress, - onLongPress, - onDelete, -}: FolderIconProps) { - const rotation = useSharedValue(0); - const scale = useSharedValue(1); - - // Jiggly animation for edit mode - useEffect(() => { - if (isEditMode) { - rotation.value = withRepeat( - withSequence( - withTiming(-2, { duration: 100 }), - withTiming(2, { duration: 100 }), - withTiming(-2, { duration: 100 }), - withTiming(2, { duration: 100 }) - ), - -1, - true - ); - } else { - rotation.value = withTiming(0, { duration: 100 }); - } - }, [isEditMode]); - - const animatedStyle = useAnimatedStyle(() => { - return { - transform: [ - { rotate: `${rotation.value}deg` }, - { scale: scale.value }, - ], - }; - }); - - const handlePressIn = () => { - scale.value = withTiming(0.95, { duration: 100 }); - }; - - const handlePressOut = () => { - scale.value = withTiming(1, { duration: 100 }); - }; - - const getAppColor = (appName: string) => { - const colors = [ - '#FF453A', '#FF9F0A', '#FFD60A', '#30D158', - '#40C8E0', '#007AFF', '#5856D6', '#AF52DE', - '#FF2D92', - ]; - const index = appName.charCodeAt(0) % colors.length; - return colors[index]; - }; - - // Render mini app icons in 3x3 grid - const renderMiniApps = () => { - const displayApps = apps.slice(0, 9); // Show max 9 apps - const emptySlots = 9 - displayApps.length; - - return ( - - {displayApps.map((app, index) => ( - - - - {app.name.charAt(0).toUpperCase()} - - - - ))} - {/* Fill empty slots */} - {Array.from({ length: emptySlots }).map((_, index) => ( - - ))} - - ); - }; - - return ( - - - {/* Folder Background */} - - {renderMiniApps()} - - - {/* Delete button */} - {isEditMode && ( - - - × - - - )} - - - {/* Folder Name */} - - {name} - - - ); -} - -const styles = StyleSheet.create({ - container: { - width: 74, - height: 90, - alignItems: 'center', - }, - iconWrapper: { - position: 'relative', - }, - folderContainer: { - width: 60, - height: 60, - borderRadius: Apple.Radius.medium, - padding: 8, - overflow: 'hidden', - }, - miniAppsGrid: { - flex: 1, - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - alignContent: 'space-between', - }, - miniAppIcon: { - width: '30%', - height: '30%', - padding: 1, - }, - miniAppInner: { - width: '100%', - height: '100%', - borderRadius: 3, - justifyContent: 'center', - alignItems: 'center', - }, - miniAppInitial: { - fontSize: 6, - fontWeight: '600', - color: 'white', - }, - folderName: { - marginTop: 6, - fontSize: Apple.Typography.caption1.fontSize, - fontWeight: Apple.Typography.caption1.fontWeight, - lineHeight: Apple.Typography.caption1.lineHeight, - color: Apple.Colors.label, - textAlign: 'center', - width: '100%', - paddingHorizontal: 2, - }, - deleteButton: { - position: 'absolute', - top: -8, - left: -8, - width: 24, - height: 24, - borderRadius: 12, - backgroundColor: Apple.Colors.systemRed, - justifyContent: 'center', - alignItems: 'center', - ...Apple.Shadows.level2, - }, - deleteButtonInner: { - width: 20, - height: 20, - borderRadius: 10, - backgroundColor: Apple.Colors.systemRed, - justifyContent: 'center', - alignItems: 'center', - }, - deleteButtonText: { - color: 'white', - fontSize: 18, - fontWeight: '300', - lineHeight: 18, - marginTop: -2, - }, -}); diff --git a/src/components/apps/IOSDragDropAppsScreen.tsx b/src/components/apps/IOSDragDropAppsScreen.tsx index 3c08e21..a45fabe 100644 --- a/src/components/apps/IOSDragDropAppsScreen.tsx +++ b/src/components/apps/IOSDragDropAppsScreen.tsx @@ -10,14 +10,12 @@ import { Text, TouchableWithoutFeedback, LayoutAnimation, - LayoutChangeEvent, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { LinearGradient } from 'expo-linear-gradient'; import { Apple } from '@/constants/AppleDesign'; import { DraggableAppIcon } from '@/src/components/apps/DraggableAppIcon'; -import { IOSAppIcon } from '@/src/components/apps/IOSAppIcon'; -import { IOSFolderIcon } from '@/src/components/apps/IOSFolderIcon'; +// Icon components are used within DraggableAppIcon import { IOSSearchWidget } from '@/src/components/apps/IOSSearchWidget'; import { IOSFolderModal } from '@/src/components/apps/IOSFolderModal'; import { SafariBrowser } from '@/src/components/apps/SafariBrowser'; @@ -41,6 +39,8 @@ import { const { width, height } = Dimensions.get('window'); const ICONS_PER_ROW = 4; +const ROWS_PER_PAGE = 5; // mimic iOS grid height +const ICONS_PER_PAGE = ICONS_PER_ROW * ROWS_PER_PAGE; const ICON_SIZE = 74; const HORIZONTAL_PADDING = 20; const ICON_SPACING = (width - HORIZONTAL_PADDING * 2 - ICON_SIZE * ICONS_PER_ROW) / (ICONS_PER_ROW - 1); @@ -67,6 +67,7 @@ interface Folder { } interface GridPosition { + page: number; row: number; col: number; x: number; @@ -94,9 +95,12 @@ export default function IOSDragDropAppsScreen() { }); const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }); const [dropTargetIndex, setDropTargetIndex] = useState(-1); - + const [currentPage, setCurrentPage] = useState(0); + const [totalPages, setTotalPages] = useState(1); + // Grid positions for drag & drop const gridPositions = useRef([]); + const gridItems = useRef<(App | Folder)[]>([]); const scrollViewRef = useRef(null); // Animation values @@ -146,24 +150,87 @@ export default function IOSDragDropAppsScreen() { setFolders(mockFolders); }, []); - // Calculate grid positions + const recalcPositions = ( + appsList: App[], + folderList: Folder[], + ): { apps: App[]; folders: Folder[] } => { + let idx = 0; + const sortedFolders = folderList + .sort((a, b) => a.position - b.position) + .map((f) => ({ ...f, position: idx++ })); + const updatedApps = appsList.map((a) => { + if (a.folderId) return a; + const pos = idx++; + return { ...a, position: pos }; + }); + return { apps: updatedApps, folders: sortedFolders }; + }; + + const addAppToFolder = (app: App, folder: Folder) => { + const updatedFolders = folders.map((f) => + f.id === folder.id + ? { ...f, apps: [...f.apps, { ...app, folderId: folder.id, position: f.apps.length }] } + : f, + ); + const updatedApps = apps.map((a) => + a.id === app.id ? { ...a, folderId: folder.id } : a, + ); + const { apps: resApps, folders: resFolders } = recalcPositions(updatedApps, updatedFolders); + setApps(resApps); + setFolders(resFolders); + }; + + const createFolderFromApps = (dragApp: App, targetApp: App) => { + const newFolderId = `folder-${Date.now()}`; + const newFolder: Folder = { + id: newFolderId, + name: 'Folder', + color: Apple.Colors.systemBlue, + position: targetApp.position, + apps: [ + { ...targetApp, folderId: newFolderId, position: 0 }, + { ...dragApp, folderId: newFolderId, position: 1 }, + ], + }; + + const remainingApps = apps.filter((a) => a.id !== targetApp.id && a.id !== dragApp.id); + const updatedApps = [ + ...remainingApps, + { ...targetApp, folderId: newFolderId }, + { ...dragApp, folderId: newFolderId }, + ]; + const updatedFolders = [...folders, newFolder]; + const { apps: resApps, folders: resFolders } = recalcPositions(updatedApps, updatedFolders); + setApps(resApps); + setFolders(resFolders); + }; + + // Calculate grid positions and pages useEffect(() => { const standaloneApps = apps.filter(app => !app.folderId); const allItems = [...standaloneApps, ...folders].sort((a, b) => a.position - b.position); - + const positions: GridPosition[] = []; + + const pages = Math.max(1, Math.ceil(allItems.length / ICONS_PER_PAGE)); + setTotalPages(pages); + allItems.forEach((item, index) => { - const row = Math.floor(index / ICONS_PER_ROW); - const col = index % ICONS_PER_ROW; + const page = Math.floor(index / ICONS_PER_PAGE); + const indexInPage = index % ICONS_PER_PAGE; + const row = Math.floor(indexInPage / ICONS_PER_ROW); + const col = indexInPage % ICONS_PER_ROW; positions.push({ + page, row, col, - x: HORIZONTAL_PADDING + col * (ICON_SIZE + ICON_SPACING), + x: page * width + HORIZONTAL_PADDING + col * (ICON_SIZE + ICON_SPACING), y: 100 + row * (ICON_SIZE + VERTICAL_SPACING), }); }); - + gridPositions.current = positions; + gridItems.current = allItems; }, [apps, folders]); // Animate widget opacity in edit mode @@ -331,7 +398,18 @@ export default function IOSDragDropAppsScreen() { const handleDragEnd = useCallback((x: number, y: number) => { if (dropTargetIndex >= 0 && dragState.item) { - handleItemDrop(dragState.item, dropTargetIndex); + const target = gridItems.current[dropTargetIndex]; + if (target && !('apps' in dragState.item)) { + if (target && 'apps' in target) { + addAppToFolder(dragState.item as App, target as Folder); + } else if ((target as App).id !== (dragState.item as App).id) { + createFolderFromApps(dragState.item as App, target as App); + } else { + handleItemDrop(dragState.item, dropTargetIndex); + } + } else { + handleItemDrop(dragState.item, dropTargetIndex); + } } setDragState({ @@ -382,65 +460,45 @@ export default function IOSDragDropAppsScreen() { }); - const renderGrid = () => { + const renderGrid = (pageIndex: number) => { // Get all items (apps not in folders + folders) const standaloneApps = apps.filter(app => !app.folderId); const allItems = [...standaloneApps, ...folders].sort((a, b) => a.position - b.position); - + + const pageItems = allItems.slice( + pageIndex * ICONS_PER_PAGE, + (pageIndex + 1) * ICONS_PER_PAGE, + ); + const rows = []; - for (let i = 0; i < allItems.length; i += ICONS_PER_ROW) { - const rowItems = allItems.slice(i, i + ICONS_PER_ROW); + for (let i = 0; i < ROWS_PER_PAGE; i++) { + const start = i * ICONS_PER_ROW; + const rowItems = pageItems.slice(start, start + ICONS_PER_ROW); rows.push( - {rowItems.map((item, index) => { + {rowItems.map((item, indexInRow) => { const isFolder = 'apps' in item; + const itemIndex = pageIndex * ICONS_PER_PAGE + i * ICONS_PER_ROW + indexInRow; + const position = gridPositions.current[itemIndex]; const isDragging = dragState.isActive && dragState.item?.id === item.id; - + return ( - { - // Store position for drag calculations - const { x, y } = event.nativeEvent.layout; - // Update grid positions if needed - }} - > - {!isDragging && ( - isFolder ? ( - { - const rowIndex = Math.floor(i / ICONS_PER_ROW); - const colIndex = index; - const x = HORIZONTAL_PADDING + colIndex * (ICON_SIZE + ICON_SPACING) + ICON_SIZE / 2; - const y = 100 + rowIndex * (ICON_SIZE + VERTICAL_SPACING) + ICON_SIZE / 2; - handleFolderPress(item, { x, y }); - }} - onLongPress={handleLongPress} - onDelete={() => handleDeleteFolder(item.id)} - /> - ) : ( - handleAppPress(item)} - onLongPress={handleLongPress} - onDelete={() => handleDeleteApp(item.id)} - /> - ) - )} - + (isFolder ? handleFolderPress(item as Folder, { x: position.x + ICON_SIZE / 2, y: position.y + ICON_SIZE / 2 }) : handleAppPress(item as App))} + onLongPress={handleLongPress} + onDelete={() => (isFolder ? handleDeleteFolder(item.id) : handleDeleteApp(item.id))} + onDragStart={handleDragStart} + onDragMove={handleDragMove} + onDragEnd={handleDragEnd} + isDragging={isDragging} + isDropTarget={dropTargetIndex === itemIndex} + /> ); })} {/* Fill empty slots in row */} @@ -502,21 +560,30 @@ export default function IOSDragDropAppsScreen() { { + const page = Math.round(e.nativeEvent.contentOffset.x / width); + setCurrentPage(page); + }} scrollEnabled={!isEditMode || !dragState.isActive} > - {/* App Grid */} - - {renderGrid()} - - - {/* Spacer to push widget to bottom */} - + {Array.from({ length: totalPages }).map((_, pageIndex) => ( + + {renderGrid(pageIndex)} + + ))} + + + {Array.from({ length: totalPages }).map((_, idx) => ( + + ))} + {/* Search Widget at bottom */} @@ -620,10 +687,6 @@ const styles = StyleSheet.create({ scrollView: { flex: 1, }, - scrollContent: { - flexGrow: 1, - paddingTop: 10, - }, gridContainer: { paddingHorizontal: HORIZONTAL_PADDING, }, @@ -642,9 +705,20 @@ const styles = StyleSheet.create({ emptySlot: { width: ICON_SIZE, }, - spacer: { - flex: 1, - minHeight: 140, + pageIndicator: { + flexDirection: 'row', + justifyContent: 'center', + marginTop: 8, + }, + pageDot: { + width: 6, + height: 6, + borderRadius: 3, + backgroundColor: 'rgba(255,255,255,0.3)', + marginHorizontal: 3, + }, + activePageDot: { + backgroundColor: 'rgba(255,255,255,0.8)', }, widgetContainer: { position: 'absolute', diff --git a/src/components/apps/SearchWidget.tsx b/src/components/apps/SearchWidget.tsx deleted file mode 100644 index c8f0c45..0000000 --- a/src/components/apps/SearchWidget.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import { - StyleSheet, - Text, - View, - TouchableOpacity, - Dimensions, -} from 'react-native'; -import { BlurView } from 'expo-blur'; -import { Apple } from '@/constants/AppleDesign'; -import { Ionicons } from '@expo/vector-icons'; - -interface SearchWidgetProps { - onPress: () => void; -} - -const { width } = Dimensions.get('window'); -const WIDGET_WIDTH = width - 32; // 16px padding on each side - -export function SearchWidget({ onPress }: SearchWidgetProps) { - return ( - - - - - - Search or enter website - - - - - A - - Aave - - - - U - - Uniswap - - - - J - - Jumper - - - - H - - Hyperliquid - - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - width: WIDGET_WIDTH, - height: 110, - marginHorizontal: 16, - marginBottom: 16, - borderRadius: Apple.Radius.xlarge, - overflow: 'hidden', - ...Apple.Shadows.level3, - }, - blurContainer: { - flex: 1, - backgroundColor: 'rgba(28, 28, 30, 0.8)', // Fallback for blur - }, - content: { - flex: 1, - padding: 16, - }, - searchBar: { - height: 36, - backgroundColor: Apple.Colors.tertiarySystemBackground, - borderRadius: Apple.Radius.standard, - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - marginBottom: 12, - }, - searchIcon: { - marginRight: 8, - }, - placeholder: { - color: Apple.Colors.placeholderText, - fontSize: Apple.Typography.subheadline.fontSize, - fontWeight: Apple.Typography.subheadline.fontWeight, - }, - suggestionRow: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - suggestion: { - alignItems: 'center', - flex: 1, - }, - suggestionIcon: { - width: 28, - height: 28, - borderRadius: 6, - justifyContent: 'center', - alignItems: 'center', - marginBottom: 4, - }, - suggestionInitial: { - color: 'white', - fontSize: 12, - fontWeight: '600', - }, - suggestionText: { - color: Apple.Colors.secondaryLabel, - fontSize: 10, - fontWeight: Apple.Typography.caption2.fontWeight, - }, -}); From 07a6617002a02318c9e127469cddcb7dfbcce032 Mon Sep 17 00:00:00 2001 From: Arda Erturk Date: Fri, 6 Jun 2025 17:09:55 -0400 Subject: [PATCH 2/2] Refine iOS home screen interactions --- README.md | 1 + src/components/apps/DraggableAppIcon.tsx | 1 + src/components/apps/IOSDragDropAppsScreen.tsx | 34 ++++++------------- src/components/apps/IOSFolderModal.tsx | 4 +-- src/components/apps/IOSSearchWidget.tsx | 5 ++- src/components/apps/SafariBrowser.tsx | 20 +++-------- 6 files changed, 24 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 0b5fcba..9ed5445 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Interspace is a wallet wrapper that allows users to: - Link existing wallets (MetaMask, Coinbase, etc.) - Grant ERC-20 token allowances for seamless transactions +### 2. Apps - iOS inspired home screen with dynamic app icons and folders - Horizontal paging with page dots just like iOS - Drag-and-drop organization and folder creation diff --git a/src/components/apps/DraggableAppIcon.tsx b/src/components/apps/DraggableAppIcon.tsx index b5736f4..f4c73c0 100644 --- a/src/components/apps/DraggableAppIcon.tsx +++ b/src/components/apps/DraggableAppIcon.tsx @@ -160,6 +160,7 @@ export function DraggableAppIcon({ ref={panRef} onGestureEvent={panHandler} simultaneousHandlers={longPressRef} + waitFor={longPressRef} enabled={isEditMode} > diff --git a/src/components/apps/IOSDragDropAppsScreen.tsx b/src/components/apps/IOSDragDropAppsScreen.tsx index a45fabe..e45dacc 100644 --- a/src/components/apps/IOSDragDropAppsScreen.tsx +++ b/src/components/apps/IOSDragDropAppsScreen.tsx @@ -267,22 +267,12 @@ export default function IOSDragDropAppsScreen() { hapticTrigger('impactLight'); setBrowserUrl(app.url); - // Animate browser opening from widget position - const widgetY = height - 200; // Approximate widget position - browserScale.value = 0; + browserScale.value = 0.9; browserOpacity.value = 0; - - // Set initial position - browserScale.value = withSequence( - withTiming(0.3, { duration: 0 }), - withSpring(1, { - damping: 20, - stiffness: 350, - }) - ); - - browserOpacity.value = withTiming(1, { duration: 300 }); - backgroundBlur.value = withTiming(10, { duration: 300 }); + + browserScale.value = withTiming(1, { duration: 250 }); + browserOpacity.value = withTiming(1, { duration: 250 }); + backgroundBlur.value = withTiming(10, { duration: 250 }); setBrowserVisible(true); }, [isEditMode, dragState.isActive]); @@ -344,12 +334,10 @@ export default function IOSDragDropAppsScreen() { const handleSearchPress = useCallback(() => { hapticTrigger('impactLight'); - // Animate browser opening from widget - browserScale.value = withSpring(1, { - damping: 18, - stiffness: 300, - overshootClamping: false, - }); + browserScale.value = 0.9; + browserOpacity.value = 0; + + browserScale.value = withTiming(1, { duration: 250 }); browserOpacity.value = withTiming(1, { duration: 250 }); backgroundBlur.value = withTiming(10, { duration: 250 }); @@ -526,9 +514,9 @@ export default function IOSDragDropAppsScreen() { {/* iOS 17 style gradient background */} diff --git a/src/components/apps/IOSFolderModal.tsx b/src/components/apps/IOSFolderModal.tsx index 143f845..7e97d69 100644 --- a/src/components/apps/IOSFolderModal.tsx +++ b/src/components/apps/IOSFolderModal.tsx @@ -24,8 +24,8 @@ import { IOSAppIcon } from './IOSAppIcon'; import { hapticTrigger } from '@/src/utils/hapticFeedback'; const { width, height } = Dimensions.get('window'); -const FOLDER_WIDTH = width - 40; -const FOLDER_HEIGHT = 340; +const FOLDER_WIDTH = width - 30; +const FOLDER_HEIGHT = 380; const ICONS_PER_ROW = 3; const ICON_SIZE = 74; const ICON_SPACING = 25; diff --git a/src/components/apps/IOSSearchWidget.tsx b/src/components/apps/IOSSearchWidget.tsx index 22f58a3..4f489a8 100644 --- a/src/components/apps/IOSSearchWidget.tsx +++ b/src/components/apps/IOSSearchWidget.tsx @@ -44,6 +44,7 @@ const DEFAULT_RECENT_APPS = [ export function IOSSearchWidget({ onPress, recentApps = DEFAULT_RECENT_APPS }: IOSSearchWidgetProps) { const scale = useSharedValue(1); const opacity = useSharedValue(1); + const blur = useSharedValue(60); const animatedStyle = useAnimatedStyle(() => { return { @@ -58,6 +59,7 @@ export function IOSSearchWidget({ onPress, recentApps = DEFAULT_RECENT_APPS }: I stiffness: 400, }); opacity.value = withTiming(0.9, { duration: 100 }); + blur.value = withTiming(80, { duration: 200 }); }; const handlePressOut = () => { @@ -66,6 +68,7 @@ export function IOSSearchWidget({ onPress, recentApps = DEFAULT_RECENT_APPS }: I stiffness: 350, }); opacity.value = withTiming(1, { duration: 100 }); + blur.value = withTiming(60, { duration: 200 }); }; return ( @@ -80,7 +83,7 @@ export function IOSSearchWidget({ onPress, recentApps = DEFAULT_RECENT_APPS }: I {/* Glass morphism background */} - + {/* Gradient overlay for glass effect */} { if (visible) { hapticTrigger('impactLight'); - translateY.value = withSpring(0, { - damping: 25, - stiffness: 400, - mass: 0.8, - }); - scale.value = withSpring(1, { - damping: 20, - stiffness: 350, - }); + translateY.value = withTiming(0, { duration: 250 }); + scale.value = withTiming(1, { duration: 250 }); opacity.value = withTiming(1, { duration: 250 }); - borderRadius.value = withTiming(0, { duration: 300 }); + borderRadius.value = withTiming(0, { duration: 250 }); } else { - translateY.value = withSpring(height, { - damping: 25, - stiffness: 400, - }); - scale.value = withTiming(0.9, { duration: 200 }); + translateY.value = withTiming(height, { duration: 200 }); + scale.value = withTiming(0.95, { duration: 200 }); opacity.value = withTiming(0, { duration: 200 }); borderRadius.value = withTiming(40, { duration: 200 }); }