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..ca79cfa37 100644 --- a/src/screens/SelectLineScreen.tsx +++ b/src/screens/SelectLineScreen.tsx @@ -45,10 +45,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 +66,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 +153,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 +302,47 @@ const SelectLineScreen = () => { // --- JSX --- return ( <> - + {/* + 上下のセーフエリアは絶対配置オーバーレイの NowHeader / FooterTabBar が + それぞれ処理するため上下インセットは無効化する。一方、横向き/タブレットの + ノッチでコンテンツが欠けないよう、左右インセットだけは SafeAreaView で確保する。 + */} + + {/* + プリセットカードは上部に固定表示し、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 && (