From 902cd0c4ec1e7600c1ca60e044f7403d9f0d2098 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 23:34:34 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E5=88=9D=E6=9C=9F=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AEPull=20to=20Refresh=E3=81=AE=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?=E3=82=92=E3=83=97=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=82=88=E3=82=8A=E4=B8=8B=E3=81=AE=E8=B7=AF?= =?UTF-8?q?=E7=B7=9A=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E3=81=BF=E3=81=AB?= =?UTF-8?q?=E9=99=90=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Gu76E8vDn9ZheWEpmQj6Db --- src/screens/SelectLineScreen.render.test.tsx | 10 ++-- src/screens/SelectLineScreen.tsx | 60 +++++++++++--------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/screens/SelectLineScreen.render.test.tsx b/src/screens/SelectLineScreen.render.test.tsx index c8c2b2038..c749030a2 100644 --- a/src/screens/SelectLineScreen.render.test.tsx +++ b/src/screens/SelectLineScreen.render.test.tsx @@ -282,16 +282,18 @@ describe('SelectLineScreen', () => { }); describe('ローディング状態', () => { - it('nearbyStationLoading=true のときスケルトンが表示される', () => { + it('nearbyStationLoading=true のとき路線リストのスケルトンが表示される', () => { setupDefaults({ nearbyStationLoading: true }); - const { getByTestId, queryByTestId } = render(); + const { getByTestId } = render(); expect(getByTestId('skeleton-placeholder')).toBeTruthy(); - expect(queryByTestId('presets')).toBeNull(); + // プリセットカードは上部固定表示になり最寄り駅のローディングとは独立したため、 + // ローディング中でも常に表示される + expect(getByTestId('presets')).toBeTruthy(); }); - it('nearbyStationLoading=false のときプリセットが表示される', () => { + it('nearbyStationLoading=false のとき路線リストのスケルトンは表示されない', () => { setupDefaults({ nearbyStationLoading: false }); const { getByTestId, queryByTestId } = render(); diff --git a/src/screens/SelectLineScreen.tsx b/src/screens/SelectLineScreen.tsx index d6cdebe11..60bfd48ce 100644 --- a/src/screens/SelectLineScreen.tsx +++ b/src/screens/SelectLineScreen.tsx @@ -16,7 +16,6 @@ import { View, } from 'react-native'; import Animated, { LinearTransition } from 'react-native-reanimated'; -import { SafeAreaView } from 'react-native-safe-area-context'; import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; import type { Line, LineNested } from '~/@types/graphql'; import { CommonCard } from '~/components/CommonCard'; @@ -45,10 +44,7 @@ import { SelectLineScreenPresets } from './SelectLineScreenPresets'; const styles = StyleSheet.create({ root: { paddingHorizontal: 24, flex: 1 }, - listContainerStyle: { - paddingBottom: 24, - paddingHorizontal: 24, - }, + listScroll: { flex: 1 }, heading: { fontSize: 24, fontWeight: 'bold', @@ -69,6 +65,10 @@ const styles = StyleSheet.create({ // https://github.com/facebook/react-native/issues/53987 const REFRESH_TINT_DELAY_MS = 500; +// 路線リストのスクロール領域下端の基本パディング。実際の下端余白はこれに +// フッタータブバーの高さ(useFooterHeight)を加算して算出する +const LIST_CONTENT_PADDING_BOTTOM = 24; + const NearbyStationLoader = () => ( @@ -152,12 +152,10 @@ const SelectLineScreen = () => { // --- 派生値 --- const footerHeight = useFooterHeight(); - const listPaddingBottom = useMemo(() => { - const flattened = StyleSheet.flatten(styles.listContainerStyle) as { - paddingBottom?: number; - }; - return (flattened?.paddingBottom ?? 24) + footerHeight; - }, [footerHeight]); + const listPaddingBottom = useMemo( + () => LIST_CONTENT_PADDING_BOTTOM + footerHeight, + [footerHeight] + ); const orientation = useDeviceOrientation(); const isPortraitOrientation = useMemo( @@ -303,36 +301,44 @@ const SelectLineScreen = () => { // --- JSX --- return ( <> - + {/* + NowHeader / FooterTabBar は絶対配置のオーバーレイで上下のセーフエリアを + それぞれ処理する。中央のコンテンツはその間に収まればよいため、コンテナ + 自体はセーフエリアを持たない View で構成する。 + */} + + {/* + プリセットカードは上部に固定表示し、Pull to Refresh の対象外にする。 + paddingTop で固定ヘッダー(NowHeader)の直下に配置する。 + */} + + + + + + + {/* プリセットカードより下の路線リストのみ Pull to Refresh の対象にする */} } - contentContainerStyle={[ - styles.listContainerStyle, - nowHeaderHeight ? { paddingTop: nowHeaderHeight } : null, - { paddingBottom: listPaddingBottom }, - ]} + contentContainerStyle={{ paddingBottom: listPaddingBottom }} > {nearbyStationLoading && !refreshing ? ( ) : ( <> - - - {stationLines.length > 0 && ( @@ -383,7 +389,7 @@ const SelectLineScreen = () => { )} - + {/* 固定ヘッダー */} Date: Mon, 13 Jul 2026 00:30:24 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E5=88=9D=E6=9C=9F=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E5=B7=A6=E5=8F=B3=E3=82=BB=E3=83=BC=E3=83=95=E3=82=A8?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=A4=E3=83=B3=E3=82=BB=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=92SafeAreaView=E3=81=A7=E7=A2=BA=E4=BF=9D=E3=81=97?= =?UTF-8?q?=E3=81=A6=E6=A8=AA=E5=90=91=E3=81=8D/=E3=82=BF=E3=83=96?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=83=88=E3=81=AE=E3=83=8E=E3=83=83=E3=83=81?= =?UTF-8?q?=E6=AC=A0=E3=81=91=E3=82=92=E9=98=B2=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Gu76E8vDn9ZheWEpmQj6Db --- src/screens/SelectLineScreen.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/screens/SelectLineScreen.tsx b/src/screens/SelectLineScreen.tsx index 60bfd48ce..ca79cfa37 100644 --- a/src/screens/SelectLineScreen.tsx +++ b/src/screens/SelectLineScreen.tsx @@ -16,6 +16,7 @@ import { View, } from 'react-native'; import Animated, { LinearTransition } from 'react-native-reanimated'; +import { SafeAreaView } from 'react-native-safe-area-context'; import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; import type { Line, LineNested } from '~/@types/graphql'; import { CommonCard } from '~/components/CommonCard'; @@ -302,11 +303,14 @@ const SelectLineScreen = () => { return ( <> {/* - NowHeader / FooterTabBar は絶対配置のオーバーレイで上下のセーフエリアを - それぞれ処理する。中央のコンテンツはその間に収まればよいため、コンテナ - 自体はセーフエリアを持たない View で構成する。 + 上下のセーフエリアは絶対配置オーバーレイの NowHeader / FooterTabBar が + それぞれ処理するため上下インセットは無効化する。一方、横向き/タブレットの + ノッチでコンテンツが欠けないよう、左右インセットだけは SafeAreaView で確保する。 */} - + {/* プリセットカードは上部に固定表示し、Pull to Refresh の対象外にする。 paddingTop で固定ヘッダー(NowHeader)の直下に配置する。 @@ -389,7 +393,7 @@ const SelectLineScreen = () => { )} - + {/* 固定ヘッダー */}