From e28e28bd43a0f0b136d92b07f15fcbdb8bd5aa54 Mon Sep 17 00:00:00 2001 From: KimByeongHun Date: Wed, 27 May 2026 16:21:15 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EA=B2=80=EC=82=AC=20=EC=A4=91=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=9D=B4=ED=83=88=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/(home)/scanning.tsx | 31 ++++++++++++++++++++++++++++++- app/(tabs)/_layout.tsx | 27 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/app/(tabs)/(home)/scanning.tsx b/app/(tabs)/(home)/scanning.tsx index a258e41..bbfd2d1 100644 --- a/app/(tabs)/(home)/scanning.tsx +++ b/app/(tabs)/(home)/scanning.tsx @@ -2,11 +2,12 @@ import { useAuth } from '@clerk/expo'; import LottieView from 'lottie-react-native'; import { router, Stack, useLocalSearchParams } from 'expo-router'; import { useEffect, useRef, useState } from 'react'; -import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { BackHandler, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { fetchAnalysis, requestAnalysis, type AnalysisResponse, type AnalysisVerdict } from '@/api/analyses'; import { ApiError } from '@/api/api-client'; import { Colors, Typography } from '@/constants/theme'; +import { AppIcon } from '@/components/ui/app-icon'; const POLLING_INTERVAL_MS = 2_000; const MAX_POLLING_MS = 30_000; @@ -88,6 +89,19 @@ export default function ScanningScreen() { }, [isLoaded, isSignedIn, retryKey, url]); const hasError = errorMessage.length > 0; + const isScanning = !hasError; + + useEffect(() => { + if (!isScanning) { + return undefined; + } + + const subscription = BackHandler.addEventListener('hardwareBackPress', () => true); + + return () => { + subscription.remove(); + }; + }, [isScanning]); return ( <> @@ -100,6 +114,17 @@ export default function ScanningScreen() { headerTitleStyle: { ...Typography.title, color: Colors.brand.text }, headerTintColor: Colors.brand.text, headerShadowVisible: false, + headerBackVisible: !isScanning, + gestureEnabled: !isScanning, + headerLeft: isScanning + ? () => ( + + ) + : undefined, }} /> @@ -298,6 +323,10 @@ const styles = StyleSheet.create({ paddingHorizontal: 24, paddingTop: 40, }, + headerBackButton: { + width: 44, + height: 44, + }, animationWrapper: { width: 280, height: 280, diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 3a453e0..d270c39 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -20,12 +20,36 @@ const TAB_TO_HREF: Partial> = { folder: '/(tabs)/(folder)', }; +type NestedRoute = { + name?: string; + state?: { + index?: number; + routes?: NestedRoute[]; + }; +}; + +function getActiveNestedRouteName(route?: NestedRoute): string | undefined { + const nestedState = route?.state; + + if (!nestedState?.routes?.length) { + return route?.name; + } + + const nestedIndex = nestedState.index ?? 0; + return getActiveNestedRouteName(nestedState.routes[nestedIndex]); +} + function CustomTabBar({ state }: BottomTabBarProps) { const activeRoute = state.routes[state.index]; const activeRouteName = activeRoute?.name ?? '(home)'; const activeTab = ROUTE_TO_TAB[activeRouteName] ?? 'home'; + const isScanningRoute = getActiveNestedRouteName(activeRoute as NestedRoute) === 'scanning'; function handleTabPress(tab: TabVariant) { + if (isScanningRoute) { + return; + } + const href = TAB_TO_HREF[tab]; if (href) { router.navigate(href as any); @@ -36,6 +60,9 @@ function CustomTabBar({ state }: BottomTabBarProps) { ); }