diff --git a/components/ActionSheetComponents/EachTrainInfo.tsx b/components/ActionSheetComponents/EachTrainInfo.tsx
index 416f966..8aa50da 100644
--- a/components/ActionSheetComponents/EachTrainInfo.tsx
+++ b/components/ActionSheetComponents/EachTrainInfo.tsx
@@ -1,4 +1,4 @@
-import React, { useRef } from "react";
+import React, { useRef, useState } from "react";
import { Platform } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { EachTrainInfoCore } from "./EachTrainInfoCore";
@@ -6,6 +6,16 @@ import { useSheetMaxHeight } from "./useSheetMaxHeight";
export const EachTrainInfo = ({ payload }) => {
const actionSheetRef = useRef(null);
const maxHeight = useSheetMaxHeight();
+ const [sheetOpened, setSheetOpened] = useState(false);
+
+ const handleOpen = () => {
+ setSheetOpened(true);
+ };
+
+ const handleClose = () => {
+ setSheetOpened(false);
+ };
+
if (!payload) return <>>;
return (
{
drawUnderStatusBar={false}
isModal={Platform.OS === "ios" && !Platform.isPad}
containerStyle={{ maxHeight }}
-
+ onOpen={handleOpen}
+ onClose={handleClose}
//useBottomSafeAreaPadding={Platform.OS == "android"}
>
-
+
);
};
diff --git a/components/ActionSheetComponents/EachTrainInfoCore.tsx b/components/ActionSheetComponents/EachTrainInfoCore.tsx
index 721b572..4e37c4f 100644
--- a/components/ActionSheetComponents/EachTrainInfoCore.tsx
+++ b/components/ActionSheetComponents/EachTrainInfoCore.tsx
@@ -22,6 +22,7 @@ import { ShowSpecialTrain } from "./EachTrainInfo/ShowSpecialTrain";
import { useTrainMenu } from "../../stateBox/useTrainMenu";
import { HeaderText } from "./EachTrainInfoCore/HeaderText";
import { useStationList } from "../../stateBox/useStationList";
+import { useCurrentTrain } from "../../stateBox/useCurrentTrain";
import { useThemeColors } from "@/lib/theme";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { useResponsive } from "@/lib/responsive";
@@ -41,6 +42,7 @@ export const EachTrainInfoCore = ({
openStationACFromEachTrainInfo,
from,
navigate,
+ sheetOpened = false,
}) => {
const { stationList } = useStationList();
const { allCustomTrainData } = useAllTrainDiagram();
@@ -49,6 +51,7 @@ export const EachTrainInfoCore = ({
const { setTrainInfo } = useTrainMenu();
const { height } = useWindowDimensions();
const { isLandscape } = useDeviceOrientationChange();
+ const { getCurrentStationData } = useCurrentTrain();
const scrollRef = useRef(null);
// Custom hooks for data management
@@ -72,7 +75,9 @@ export const EachTrainInfoCore = ({
} = useExtendedStations(trainData, setTrainData);
// UI state
- const [showThrew, setShowThrew] = useState(false);
+ // 走行中の列車は初期状態から通過駅を表示する(後から showThrew を true に変更すると
+ // ActionSheet の onSheetLayout が再発火してスプリングアニメーションが途中でリスタートするため)
+ const [showThrew, setShowThrew] = useState(() => !!getCurrentStationData(data.trainNum));
const [isJumped, setIsJumped] = useState(false);
// Auto scroll to current position
@@ -82,7 +87,7 @@ export const EachTrainInfoCore = ({
scrollRef,
isJumped,
setIsJumped,
- setShowThrew
+ sheetOpened
);
// Back button handler
diff --git a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useAutoScroll.ts b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useAutoScroll.ts
index e719341..5374558 100644
--- a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useAutoScroll.ts
+++ b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useAutoScroll.ts
@@ -1,5 +1,4 @@
import { useEffect, MutableRefObject } from 'react';
-import { InteractionManager } from 'react-native';
export const useAutoScroll = (
@@ -8,31 +7,27 @@ export const useAutoScroll = (
scrollRef: MutableRefObject,
isJumped: boolean,
setIsJumped: (value: boolean) => void,
- setShowThrew: (value: boolean) => void
+ sheetOpened: boolean = false
) => {
useEffect(() => {
- if (isJumped || !points?.length || !scrollRef) return;
+ // ActionSheetのスプリングアニメーション完了後まで待機
+ if (!sheetOpened || isJumped || !points?.length || !scrollRef) return;
const currentPositionIndex = points.findIndex((d) => d === true);
if (currentPositionIndex === -1) return;
- // ActionSheetの開閉アニメーション完了後にレイアウト変更を行う
- const handle = InteractionManager.runAfterInteractions(() => {
- setShowThrew(true);
+ // 5駅以内の場合はスクロールしない
+ if (currentPositionIndex < 5) {
+ setIsJumped(true);
+ return;
+ }
- // 5駅以内の場合はスクロールしない
- if (currentPositionIndex < 5) {
- setIsJumped(true);
- return;
- }
+ const scrollPosition = currentPositionIndex * 44 - 50;
+ const timer = setTimeout(() => {
+ scrollRef.current?.scrollTo({ y: scrollPosition, animated: true });
+ setIsJumped(true);
+ }, 100);
- const scrollPosition = currentPositionIndex * 44 - 50;
- setTimeout(() => {
- scrollRef.current?.scrollTo({ y: scrollPosition, animated: true });
- setIsJumped(true);
- }, 100);
- });
-
- return () => handle.cancel();
- }, [points, trainDataWithThrough, scrollRef, isJumped, setIsJumped, setShowThrew]);
+ return () => clearTimeout(timer);
+ }, [sheetOpened, points, trainDataWithThrough, scrollRef, isJumped, setIsJumped]);
};
diff --git a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useStopStationIDs.ts b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useStopStationIDs.ts
index 30b4647..468a42f 100644
--- a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useStopStationIDs.ts
+++ b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useStopStationIDs.ts
@@ -1,25 +1,25 @@
import { useState, useEffect } from 'react';
import { useStationList } from '@/stateBox/useStationList';
+const computeStopStationIDs = (data: string[], stationList: any[][]): string[][] =>
+ data.map((item) => {
+ const [stationName] = item.split(',');
+ return stationList
+ .map((lineStations) => lineStations.filter((s) => s.StationName === stationName))
+ .reduce((acc, s) => acc.concat(s), [])
+ .map((s) => s.StationNumber);
+ });
+
export const useStopStationIDs = (trainDataWithThrough: string[]) => {
const { stationList } = useStationList();
- const [stopStationIDList, setStopStationIDList] = useState([]);
+
+ // 初回レンダリング時に同期的に計算することでActionSheetのアニメーション中の高さ変化を防ぐ
+ const [stopStationIDList, setStopStationIDList] = useState(() =>
+ computeStopStationIDs(trainDataWithThrough, stationList)
+ );
useEffect(() => {
- const stationIDs = trainDataWithThrough.map((item) => {
- const [stationName] = item.split(',');
-
- const matchingStations = stationList
- .map((lineStations) =>
- lineStations.filter((station) => station.StationName === stationName)
- )
- .reduce((acc, stations) => acc.concat(stations), [])
- .map((station) => station.StationNumber);
-
- return matchingStations;
- });
-
- setStopStationIDList(stationIDs);
+ setStopStationIDList(computeStopStationIDs(trainDataWithThrough, stationList));
}, [trainDataWithThrough, stationList]);
return stopStationIDList;
diff --git a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useThroughStations.ts b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useThroughStations.ts
index aed14c3..8fef145 100644
--- a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useThroughStations.ts
+++ b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useThroughStations.ts
@@ -2,107 +2,114 @@ import { useState, useEffect } from 'react';
import { lineListPair, stationIDPair } from '@/lib/getStationList';
import { useStationList } from '@/stateBox/useStationList';
-export const useThroughStations = (trainData) => {
- const { originalStationList, stationList } = useStationList();
- const [trainDataWithThrough, setTrainDataWithThrough] = useState([]);
- const [haveThrough, setHaveThrough] = useState(false);
+const computeThroughStations = (
+ trainData: string[],
+ stationList: any[][],
+ originalStationList: Record
+): { trainDataWithThrough: string[]; haveThrough: boolean } => {
+ if (!trainData.length) return { trainDataWithThrough: [], haveThrough: false };
- useEffect(() => {
- if (!trainData.length) {
- setTrainDataWithThrough([]);
- return;
+ let haveThrough = false;
+ const isCancel: boolean[] = [];
+
+ const stopStationList = trainData.map((item, index, array) => {
+ const [station, se] = item.split(',');
+ const [, nextSe] = array[index + 1]?.split(',') || [];
+
+ if (nextSe) {
+ // 運休判定ロジック:
+ // 1. 両方が休系(休編、休発、休着など)→ 運休区間
+ // 2. 着/着編 → 休発/休発編:到着後に運休開始 → 通過駅は通常運行
+ // 3. 休着/休着編 → 発/発編:運休終了後に出発 → 通過駅は通常運行
+ // 4. その他の休の組み合わせ → 運休区間
+ const bothCanceled = se.includes('休') && nextSe.includes('休');
+ const normalArrivalToSuspendStart =
+ (se === '着' || se === '着編') && (nextSe.includes('休') && nextSe.includes('発'));
+ const suspendEndToNormalDeparture =
+ (se.includes('休') && se.includes('着')) && (nextSe === '発' || nextSe === '発編');
+
+ isCancel.push(bothCanceled && !normalArrivalToSuspendStart && !suspendEndToNormalDeparture);
}
- const isCancel = [];
- const stopStationList = trainData.map((item, index, array) => {
- const [station, se] = item.split(',');
- const [, nextSe] = array[index + 1]?.split(',') || [];
+ if (se === '通編') haveThrough = true;
- if (nextSe) {
- // 運休判定ロジック:
- // 1. 両方が休系(休編、休発、休着など)→ 運休区間
- // 2. 着/着編 → 休発/休発編:到着後に運休開始 → 通過駅は通常運行
- // 3. 休着/休着編 → 発/発編:運休終了後に出発 → 通過駅は通常運行
- // 4. その他の休の組み合わせ → 運休区間
- const bothCanceled = se.includes('休') && nextSe.includes('休');
- const normalArrivalToSuspendStart =
- (se === '着' || se === '着編') && (nextSe.includes('休') && nextSe.includes('発'));
- const suspendEndToNormalDeparture =
- (se.includes('休') && se.includes('着')) && (nextSe === '発' || nextSe === '発編');
-
- const isCanceled = bothCanceled && !normalArrivalToSuspendStart && !suspendEndToNormalDeparture;
- isCancel.push(isCanceled);
+ return stationList.map((a) => a.filter((d) => d.StationName === station));
+ });
+
+ const allThroughStationList = stopStationList.map((firstItem, index, array) => {
+ if (index === array.length - 1) return [];
+
+ const secondItem = array[index + 1];
+ let betweenStationLine = '';
+ let baseStationNumberFirst = '';
+ let baseStationNumberSecond = '';
+
+ Object.keys(stationIDPair).forEach((lineName, lineIndex) => {
+ if (!lineName) return;
+ const haveFirst = firstItem[lineIndex];
+ const haveSecond = secondItem[lineIndex];
+
+ if (haveFirst?.length && haveSecond?.length) {
+ betweenStationLine = lineName;
+ baseStationNumberFirst = haveFirst[0].StationNumber;
+ baseStationNumberSecond = haveSecond[0].StationNumber;
}
-
- if (se === '通編') setHaveThrough(true);
-
- return stationList.map((a) => a.filter((d) => d.StationName === station));
});
- const allThroughStationList = stopStationList.map((firstItem, index, array) => {
- if (index === array.length - 1) return [];
+ if (!betweenStationLine) return [];
- const secondItem = array[index + 1];
- let betweenStationLine = '';
- let baseStationNumberFirst = '';
- let baseStationNumberSecond = '';
+ const allThroughStation: string[] = [];
+ let reverse = false;
- Object.keys(stationIDPair).forEach((lineName, lineIndex) => {
- if (!lineName) return;
- const haveFirst = firstItem[lineIndex];
- const haveSecond = secondItem[lineIndex];
+ originalStationList[lineListPair[stationIDPair[betweenStationLine]]]?.forEach((station) => {
+ const throughStatus = isCancel[index] ? '通休編' : '通過';
- if (haveFirst?.length && haveSecond?.length) {
- betweenStationLine = lineName;
- baseStationNumberFirst = haveFirst[0].StationNumber;
- baseStationNumberSecond = haveSecond[0].StationNumber;
- }
- });
-
- if (!betweenStationLine) return [];
-
- const allThroughStation = [];
- let reverse = false;
-
- originalStationList[lineListPair[stationIDPair[betweenStationLine]]]?.forEach((station) => {
- const throughStatus = isCancel[index] ? '通休編' : '通過';
-
- if (
- station.StationNumber > baseStationNumberFirst &&
- station.StationNumber < baseStationNumberSecond
- ) {
- allThroughStation.push(`${station.Station_JP},${throughStatus},`);
- setHaveThrough(true);
- reverse = false;
- } else if (
- station.StationNumber < baseStationNumberFirst &&
- station.StationNumber > baseStationNumberSecond
- ) {
- allThroughStation.push(`${station.Station_JP},${throughStatus},`);
- setHaveThrough(true);
- reverse = true;
- }
- });
-
- if (reverse) allThroughStation.reverse();
- return allThroughStation;
+ if (
+ station.StationNumber > baseStationNumberFirst &&
+ station.StationNumber < baseStationNumberSecond
+ ) {
+ allThroughStation.push(`${station.Station_JP},${throughStatus},`);
+ haveThrough = true;
+ reverse = false;
+ } else if (
+ station.StationNumber < baseStationNumberFirst &&
+ station.StationNumber > baseStationNumberSecond
+ ) {
+ allThroughStation.push(`${station.Station_JP},${throughStatus},`);
+ haveThrough = true;
+ reverse = true;
+ }
});
- let mainArray = [...trainData];
- let offset = 0;
+ if (reverse) allThroughStation.reverse();
+ return allThroughStation;
+ });
- trainData.forEach((_, index) => {
- offset += 1;
- const throughStations = allThroughStationList[index];
-
- if (!throughStations?.length) return;
+ let mainArray = [...trainData];
+ let offset = 0;
- mainArray.splice(offset, 0, ...throughStations);
- offset += throughStations.length;
- });
+ trainData.forEach((_, index) => {
+ offset += 1;
+ const throughStations = allThroughStationList[index];
+ if (!throughStations?.length) return;
+ mainArray.splice(offset, 0, ...throughStations);
+ offset += throughStations.length;
+ });
- setTrainDataWithThrough(mainArray);
+ return { trainDataWithThrough: mainArray, haveThrough };
+};
+
+export const useThroughStations = (trainData) => {
+ const { originalStationList, stationList } = useStationList();
+
+ // 初回レンダリング時に同期的に計算することでActionSheetのアニメーション中の高さ変化を防ぐ
+ const [state, setState] = useState(() =>
+ computeThroughStations(trainData, stationList, originalStationList)
+ );
+
+ useEffect(() => {
+ setState(computeThroughStations(trainData, stationList, originalStationList));
}, [trainData, stationList, originalStationList]);
- return { trainDataWithThrough, haveThrough };
+ return { trainDataWithThrough: state.trainDataWithThrough, haveThrough: state.haveThrough };
};
diff --git a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useTrainDiagramData.ts b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useTrainDiagramData.ts
index 9e098cb..f94293a 100644
--- a/components/ActionSheetComponents/EachTrainInfoCore/hooks/useTrainDiagramData.ts
+++ b/components/ActionSheetComponents/EachTrainInfoCore/hooks/useTrainDiagramData.ts
@@ -2,28 +2,34 @@ import { useState, useEffect } from 'react';
import { useAllTrainDiagram } from '@/stateBox/useAllTrainDiagram';
import { searchSpecialTrain } from '@/lib/eachTrainInfoCoreLib/searchSpecialTrain';
+const parseTrainData = (trainNum: string, trainList: Record) => {
+ if (!trainNum) return { data: [], trueIDs: [] };
+ const TD = trainList[trainNum];
+ if (!TD) {
+ const specialTrainActualIDs = searchSpecialTrain(trainNum, trainList);
+ return { data: [], trueIDs: specialTrainActualIDs || [] };
+ }
+ return { data: TD.split('#').filter((d) => d !== ''), trueIDs: [] };
+};
+
export const useTrainDiagramData = (trainNum) => {
const { allTrainDiagram: trainList } = useAllTrainDiagram();
- const [trainData, setTrainData] = useState([]);
- const [trueTrainID, setTrueTrainID] = useState([]);
const [isManuallyExtended, setIsManuallyExtended] = useState(false);
+ // 初回レンダリング時にコンテキストから同期的にデータを取得することで
+ // ActionSheetのアニメーション中に高さが変わるのを防ぐ
+ const [trainData, setTrainData] = useState(() => parseTrainData(trainNum, trainList).data);
+ const [trueTrainID, setTrueTrainID] = useState(() => parseTrainData(trainNum, trainList).trueIDs);
+
useEffect(() => {
if (!trainNum) return;
-
+
// 手動で拡張されている場合は上書きしない
if (isManuallyExtended) return;
-
- const TD = trainList[trainNum];
-
- if (!TD) {
- const specialTrainActualIDs = searchSpecialTrain(trainNum, trainList);
- setTrueTrainID(specialTrainActualIDs || []);
- setTrainData([]);
- return;
- }
-
- setTrainData(TD.split('#').filter((d) => d !== ''));
+
+ const { data, trueIDs } = parseTrainData(trainNum, trainList);
+ setTrueTrainID(trueIDs);
+ setTrainData(data);
}, [trainNum, trainList, isManuallyExtended]);
const setTrainDataExtended = (data) => {