Files
jrshikoku/components/Apps/FixedPositionBox/FixedTrainBox.tsx
harukin-expo-dev-env dc3d250466 fix: 次駅表示をDirection非依存に修正(JS/Kotlin両方)
- JS側: currentPosition[0]ではなくstopStationIDList上のmax(idx0,idx1)で進行方向の駅を判定
- Kotlin側: pollRunnable復活、allStationsのダイヤ順でmaxOf(idx0,idx1)で向かう駅を判定
- Kotlin at-station: 停車中は現在駅を表示(JS側と統一)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 01:39:31 +00:00

1056 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lineColorList from "@/assets/originData/lineColorList";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { useCurrentTrain } from "@/stateBox/useCurrentTrain";
import { useStationList } from "@/stateBox/useStationList";
import { StationProps } from "@/lib/CommonTypes";
import { FC, useEffect, useRef, useState } from "react";
import {
Text,
TouchableOpacity,
View,
Image,
LayoutAnimation,
ScrollView,
Platform,
PermissionsAndroid,
AppState,
} from "react-native";
import { getTrainType } from "@/lib/getTrainType";
import { trainDataType, trainPosition } from "@/lib/trainPositionTextArray";
import { StationNumberMaker } from "@/components/駅名表/StationNumberMaker";
import { lineListPair, stationIDPair } from "@/lib/getStationList";
import { findReversalPoints } from "@/lib/eachTrainInfoCoreLib/findReversalPoints";
import { CustomTrainData, trainTypeID } from "@/lib/CommonTypes";
import { getCurrentTrainData } from "@/lib/getCurrentTrainData";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
import { useThemeColors } from "@/lib/theme";
import {
startTrainFollowActivity,
updateTrainFollowActivity,
endTrainFollowActivity,
isAvailable as isLiveActivityAvailable,
} from "expo-live-activity";
type props = {
trainID: string;
};
export const FixedTrain: FC<props> = ({ trainID }) => {
const { colors, fixed } = useThemeColors();
const {
setFixedPosition,
currentTrain,
getCurrentStationData,
getPosition,
fixedPositionSize,
setFixedPositionSize,
liveNotificationActive,
setLiveNotificationActive,
} = useCurrentTrain();
const { mapSwitch } = useTrainMenu();
const { allCustomTrainData, allTrainDiagram } = useAllTrainDiagram();
const [liveNotifyId, setLiveNotifyId] = useState<string | null>(null);
const liveNotifyIdRef = useRef<string | null>(null);
const hasStartedRef = useRef(false);
const [train, setTrain] = useState<trainDataType>(null);
const [customData, setCustomData] = useState<CustomTrainData>(
getCurrentTrainData(trainID, currentTrain, allCustomTrainData)
);
useEffect(() => {
setCustomData(
getCurrentTrainData(trainID, currentTrain, allCustomTrainData)
);
}, [currentTrain, trainID]);
useEffect(() => {
const stationData = getCurrentStationData(trainID);
if (stationData) {
setTrain(stationData);
} else {
// バックグラウンドでは一時的にデータが消えることがある→フォアグラウンド時のみ終了
if (AppState.currentState === "active") {
alert("追跡していた列車が消えました。追跡を終了します。");
setFixedPosition({ type: null, value: null });
}
}
}, [trainID, currentTrain]);
const { getStationDataFromName, stationList, originalStationList } =
useStationList();
const [trainDataWidhThrough, setTrainDataWithThrough] = useState<string[]>(
[]
);
useEffect(() => {
const trainData = allTrainDiagram[trainID]?.split("#");
if (!trainData) return;
//let haveThrough = false;
//
const stopStationList = trainData.map((i) => {
const [station, se, time] = i.split(",");
//if (se == "通編") setHaveThrough(true);
return stationList.map((a) => a.filter((d) => d.StationName == station));
});
const allThroughStationList = stopStationList.map((i, index, array) => {
let allThroughStation = [];
if (index == array.length - 1) return;
const firstItem = array[index];
const secondItem = array[index + 1];
let betweenStationLine = "";
let baseStationNumberFirst = "";
let baseStationNumberSecond = "";
Object.keys(stationIDPair).forEach((d, index2) => {
if (!d) return;
const haveFirst = firstItem[index2];
const haveSecond = secondItem[index2];
if (haveFirst.length && haveSecond.length) {
betweenStationLine = d;
baseStationNumberFirst = haveFirst[0].StationNumber;
baseStationNumberSecond = haveSecond[0].StationNumber;
}
});
if (!betweenStationLine) return;
let reverse = false;
originalStationList[
lineListPair[stationIDPair[betweenStationLine]]
].forEach((d) => {
if (
d.StationNumber > baseStationNumberFirst &&
d.StationNumber < baseStationNumberSecond
) {
allThroughStation.push(`${d.Station_JP},通過,`);
//setHaveThrough(true);
reverse = false;
} else {
if (
d.StationNumber < baseStationNumberFirst &&
d.StationNumber > baseStationNumberSecond
) {
allThroughStation.push(`${d.Station_JP},通過,`);
//setHaveThrough(true);
reverse = true;
}
}
});
if (reverse) allThroughStation.reverse();
return allThroughStation;
});
let mainArray = [...trainData];
let indexs = 0;
trainData.forEach((d, index) => {
indexs = indexs + 1;
if (!allThroughStationList[index]) return;
if (allThroughStationList[index].length == 0) return;
mainArray.splice(indexs, 0, ...allThroughStationList[index]);
indexs = indexs + allThroughStationList[index].length;
});
setTrainDataWithThrough(mainArray);
}, [allTrainDiagram, stationList, trainID]);
const [stopStationIDList, setStopStationList] = useState([]);
useEffect(() => {
const x = trainDataWidhThrough.map((i) => {
const [station, se, time] = i.split(",");
const Stations = stationList.map((a) =>
a.filter((d) => d.StationName == station)
);
const StationNumbers =
Stations &&
Stations.reduce((newArray, e) => {
return newArray.concat(e);
}, []).map((d) => d.StationNumber);
return StationNumbers;
});
setStopStationList(x);
}, [trainDataWidhThrough]);
const [currentPosition, setCurrentPosition] = useState<string[]>([]);
useEffect(() => {
let position = getPosition(train);
if (stopStationIDList.length == 0) return;
if (position) {
if (position.length > 1) {
if (position[0] == "-Iyo") {
position[0] =
stopStationIDList[
stopStationIDList.findIndex((d) => d.includes("U14")) - 1
][0];
} else if (position[0] == "+Iyo") {
position[0] =
stopStationIDList[
stopStationIDList.findIndex((d) => d.includes("U14")) + 1
][0];
}
if (position[1] == "+Iyo") {
position[1] =
stopStationIDList[
stopStationIDList.findIndex((d) => d.includes("U14")) + 1
][0];
} else if (position[1] == "-Iyo") {
position[1] =
stopStationIDList[
stopStationIDList.findIndex((d) => d.includes("U14")) - 1
][0];
}
}
setCurrentPosition(position);
}
}, [train, stopStationIDList]);
const [nextStationData, setNextStationData] = useState<StationProps[]>([]);
const [untilStationData, setUntilStationData] = useState<StationProps[]>([]);
const [probably, setProbably] = useState(false);
useEffect(() => {
//棒線駅判定を入れて、棒線駅なら時間を見て分数がマイナスならcontinue;
const points = findReversalPoints(currentPosition, stopStationIDList);
if (!points) return;
if (points.length == 0) return;
let searchCountFirst = points.findIndex((d) => d == true);
let searchCountLast = points.findLastIndex((d) => d == true);
const delayTime = train?.delay == "入線" ? 0 : train?.delay;
let additionalSkipCount = 0;
// 2駅間走行中の場合: ダイヤ順で後ろのインデックスが進行方向の駅
// Direction に関係なく、travel-order で大きい方が「向かう駅」
let searchStart: number;
if (currentPosition.length === 2) {
const idx0 = stopStationIDList.findIndex(d => d.includes(currentPosition[0]));
const idx1 = stopStationIDList.findIndex(d => d.includes(currentPosition[1]));
const aheadIdx = Math.max(
idx0 >= 0 ? idx0 : -1,
idx1 >= 0 ? idx1 : -1
);
searchStart = aheadIdx >= 0 ? aheadIdx : searchCountLast;
} else {
searchStart = searchCountFirst;
}
for (
let searchCount = searchStart;
searchCount < trainDataWidhThrough.length;
searchCount++
) {
const nextPos = trainDataWidhThrough[searchCount];
if (nextPos) {
const [station, se, time] = nextPos.split(",");
// 通過駅はスキップ
if (se.includes("通")) {
continue;
}
// 駅に停車中1点一致の場合は時刻判定不要
if (searchCountFirst == searchCountLast) {
setNextStationData(getStationDataFromName(station));
break;
}
// 2駅間走行中: 時刻で既に通過済みか判定
let distanceMinute = 0;
if (time != "") {
const now = dayjs();
const hour = parseInt(time.split(":")[0]);
const distanceTime = now
.hour(hour < 4 ? hour + 24 : hour)
.minute(parseInt(time.split(":")[1]));
distanceMinute = distanceTime.diff(now, "minute") + delayTime;
if (now.hour() < 4) {
if (hour < 4) {
distanceMinute = distanceMinute - 1440;
}
}
}
if (distanceMinute >= 0) {
setNextStationData(getStationDataFromName(station));
break;
} else {
additionalSkipCount++;
continue;
}
}
}
let trainList = [];
for (
let searchCount = searchCountFirst - 1;
searchCount < points.length;
searchCount++
) {
trainList.push(trainDataWidhThrough[searchCount]);
}
if (additionalSkipCount > 0) {
trainList = trainList.slice(additionalSkipCount);
setProbably(true);
} else {
setProbably(false);
}
setUntilStationData(trainList);
}, [currentPosition, trainDataWidhThrough]);
const [ToData, setToData] = useState("");
useEffect(() => {
if (customData.to_data && customData.to_data != "") {
setToData(customData.to_data);
} else {
if (trainDataWidhThrough.length == 0) return;
setToData(
trainDataWidhThrough[trainDataWidhThrough.length - 2].split(",")[0]
);
}
}, [customData, trainDataWidhThrough]);
const [station, setStation] = useState<StationProps[]>([]);
useEffect(() => {
const data = getStationDataFromName(ToData);
setStation(data);
}, [ToData]);
const lineColor =
station.length > 0
? lineColorList[station[0]?.StationNumber.slice(0, 1)]
: "black";
//const lineColor = "red";
const customTrainType = getTrainType({
type: customData.type,
whiteMode: true,
});
const trainNameText = `${customData.train_name}${
(customData.train_num_distance !== "" && !isNaN(parseInt(customData.train_num_distance)))
? ` ${parseInt(customData.train_id) - parseInt(customData.train_num_distance)}`
: ""
}`;
// ── Station Progress for Live Notification ──
// 着のみエントリを除外(終着駅は着を許可、発・通編・通発編は保持)
const lastValidIdx = trainDataWidhThrough.reduce(
(last: number, d: string, i: number) => (d ? i : last), -1
);
const filteredTrainData = trainDataWidhThrough.filter((d, idx) => {
if (!d) return false;
const [, se] = d.split(",");
if (!se) return true;
// 着を含み発を含まないエントリは終着駅のみ許可
if (se.includes("着") && !se.includes("発")) {
return idx === lastValidIdx;
}
return true;
});
const stationStops = filteredTrainData
.filter((d) => !d.split(",")[1]?.includes("通"))
.map((d) => d.split(",")[0]);
// 全駅リスト(通過駅含む、停車/通過フラグ+乗換色付き)
// 駅名→所属路線コードのマップ構築
const stationToLineCodes: Record<string, string[]> = {};
if (originalStationList) {
Object.keys(lineListPair).forEach((lineCode: string) => {
const lineName = lineListPair[lineCode];
const stations = originalStationList[lineName];
if (!stations) return;
stations.forEach((s: StationProps) => {
if (!stationToLineCodes[s.Station_JP]) stationToLineCodes[s.Station_JP] = [];
if (!stationToLineCodes[s.Station_JP].includes(lineCode)) {
stationToLineCodes[s.Station_JP].push(lineCode);
}
});
});
}
// 現在走行中の路線コード
const runningLineCode = station.length > 0
? station[0]?.StationNumber?.slice(0, 1) || ""
: "";
const allStations = filteredTrainData
.map((d) => {
const [name, se] = d.split(",");
const isStop = !se?.includes("通");
const lineCodes = stationToLineCodes[name] || [];
// 乗換色: 走行路線以外の路線色
const transferColors = lineCodes
.filter((c) => c !== runningLineCode)
.map((c) => lineColorList[c])
.filter(Boolean);
return {
name,
isStop,
...(transferColors.length > 0 ? { transferColors } : {}),
};
});
// 全駅リスト中の現在地インデックス
const currentStationIndex = (() => {
const pos = train?.Pos || "";
if (!pos) return 0;
// Pos は "駅名" (駅にいる時) or "駅A駅B" (走行中) の形式
const posStations = pos.split("").map((s: string) =>
s.replace(/(下り)|(上り)|\(下り\)|\(上り\)/g, "").trim()
);
// 完全一致
const firstIdx = allStations.findIndex((s) => s.name === posStations[0]);
if (firstIdx >= 0) return firstIdx;
// 部分一致フォールバック
const partialIdx = allStations.findIndex((s) =>
posStations[0].includes(s.name) || s.name.includes(posStations[0])
);
if (partialIdx >= 0) return partialIdx;
return 0;
})();
const nextStationIndex = (() => {
const name = nextStationData[0]?.Station_JP;
if (!name) return -1;
const idx = stationStops.indexOf(name);
if (idx >= 0) return idx;
// 部分一致フォールバック
return stationStops.findIndex((s) => s === name || name.includes(s) || s.includes(name));
})();
// ── Live Notification ──
useEffect(() => {
liveNotifyIdRef.current = liveNotifyId;
}, [liveNotifyId]);
useEffect(() => {
return () => {
if (liveNotifyIdRef.current) {
endTrainFollowActivity(liveNotifyIdRef.current).catch(() => {});
setLiveNotificationActive(false);
}
};
}, []);
useEffect(() => {
if (!liveNotifyId || !train) return;
const delayNum = train.delay === "入線" ? 0 : parseInt(train.delay) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus =
nextStationData[0]?.Station_JP === train.Pos ? "ただいま" : "次は";
updateTrainFollowActivity(liveNotifyId, {
currentStation: train.Pos || "",
nextStation: nextStationData[0]?.Station_JP || "",
delayMinutes: delayNum,
scheduledArrival: "",
trainNumber: trainID,
trainType: customTrainType.shortName,
trainName: trainNameText,
trainTypeColor: customTrainType.color,
lineColor,
destination: ToData,
positionStatus,
delayStatus,
stationStops,
nextStationIndex: nextStationIndex >= 0 ? nextStationIndex : undefined,
allStations,
currentStationIndex,
}).catch(() => {});
}, [train, nextStationData, liveNotifyId, stationStops, nextStationIndex, currentStationIndex]);
// バナー表示と同時にLive Activityを自動開始
useEffect(() => {
if (!train || hasStartedRef.current || liveNotifyId) return;
if (!isLiveActivityAvailable()) return;
hasStartedRef.current = true;
const startActivity = async () => {
if (Platform.OS === 'android' && Platform.Version >= 33) {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) return;
}
const delayNum = train?.delay === "入線" ? 0 : parseInt(train?.delay) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus =
nextStationData[0]?.Station_JP === train?.Pos ? "ただいま" : "次は";
try {
const id = await startTrainFollowActivity({
trainNumber: trainID,
lineName: "",
destination: ToData,
currentStation: train?.Pos || "",
nextStation: nextStationData[0]?.Station_JP || "",
delayMinutes: delayNum,
scheduledArrival: "",
trainType: customTrainType.shortName,
trainName: trainNameText,
trainTypeColor: customTrainType.color,
lineColor,
positionStatus,
delayStatus,
stationStops,
nextStationIndex: nextStationIndex >= 0 ? nextStationIndex : undefined,
allStations,
currentStationIndex,
});
setLiveNotifyId(id);
setLiveNotificationActive(true);
} catch (e) {
console.warn('[LiveNotify] start error:', e);
}
};
startActivity();
}, [train]);
return (
<View
style={{ display: "flex", flexDirection: "column", flex: 1 }}
pointerEvents="box-none"
>
<View
style={{
flex: 1,
flexDirection: fixedPositionSize === 226 ? "column" : "row",
backgroundColor: "black",
//borderBottomColor: "black",
//borderBottomWidth: 2,
}}
>
<View
style={{
flexDirection: fixedPositionSize === 226 ? "row" : "column",
flex: 1,
backgroundColor: colors.background,
height: fixedPositionSize === 226 ? 200 : 50,
overflow: "hidden",
}}
>
<View
style={{
flex: fixedPositionSize === 226 ? 5 : 1,
flexDirection: "row",
}}
>
<View
style={{
backgroundColor: customTrainType.color,
flexDirection: "row",
alignContent: "center",
alignSelf: "center",
alignItems: "center",
height: "100%",
}}
>
<Image
source={{ uri: customData.train_info_img || "" }}
width={fixedPositionSize === 226 ? 23 : 14}
height={fixedPositionSize === 226 ? 26 : 17}
style={{ margin: 5 }}
/>
<View
style={{
flexDirection: fixedPositionSize === 226 ? "column" : "row",
alignContent: "center",
alignSelf: "center",
alignItems: "center",
maxWidth: fixedPositionSize === 226 ? 80 : 100,
}}
>
<Text
style={{
fontSize: trainNameText.length > 4 ? 12 : 14,
fontFamily: customTrainType.fontAvailable
? "JR-Nishi"
: undefined,
fontWeight: !customTrainType.fontAvailable
? "bold"
: undefined,
marginTop: customTrainType.fontAvailable ? 3 : 0,
color: fixed.textOnPrimary,
textAlignVertical: "center",
textAlign: "left",
}}
>
{customTrainType.shortName}
</Text>
{customData.train_name && (
<Text
style={{
fontSize: trainNameText.length > 4 ? 8 : 14,
color: fixed.textOnPrimary,
maxWidth: fixedPositionSize === 226 ? 200 : 60,
textAlignVertical: "center",
}}
>
{trainNameText}
</Text>
)}
</View>
<View
style={{
backgroundColor: customTrainType.color,
width: 10,
borderLeftColor: customTrainType.color,
borderTopColor: lineColor,
borderBottomColor: lineColor,
borderTopWidth: fixedPositionSize === 226 ? 50 : 14,
borderBottomWidth: fixedPositionSize === 226 ? 50 : 14,
borderLeftWidth: fixedPositionSize === 226 ? 30 : 10,
borderRightWidth: 0,
//height: fixedPositionSize === 226 ? 20 : 100,
height: "100%",
}}
></View>
</View>
<View
style={{
flexDirection: "row",
alignContent: "center",
alignSelf: "center",
height: "100%",
backgroundColor: lineColor,
flex: 1,
}}
>
<View
style={{
flexDirection: "row",
alignContent: "center",
alignSelf: "center",
alignItems: "center",
}}
>
<StationNumberMaker
currentStation={station}
singleSize={18}
useEach={true}
/>
<Text
style={{
fontSize: customData?.to_data?.length > 4 ? 9 : 12,
color: fixed.textOnPrimary,
fontWeight: "bold",
textAlignVertical: "center",
margin: 0,
padding: 0,
height: "100%",
}}
>
{ToData}
</Text>
</View>
</View>
</View>
{fixedPositionSize === 226 && (
<View
style={{
backgroundColor: colors.background,
width: 10,
borderLeftColor: "black",
borderTopColor: lineColor,
borderBottomColor: colors.background,
borderRightColor: "black",
borderTopWidth: 50,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 20,
}}
></View>
)}
<View
style={{
backgroundColor: "black",
flex: fixedPositionSize === 226 ? 4 : 1,
flexDirection: "row",
alignItems: "center",
}}
>
<View style={{ flexDirection: "column" }}>
<Text
style={{
fontSize: 10,
fontWeight: "bold",
color: "white",
marginHorizontal: 5,
paddingVertical: 0,
marginVertical: -1,
}}
>
{nextStationData[0]?.Station_JP == train?.Pos
? "ただいま"
: "次は"}
</Text>
{probably && (
<Text
style={{
fontSize: 5,
color: "white",
fontWeight: "bold",
marginHorizontal: 5,
paddingVertical: 0,
marginVertical: -1,
}}
>
()
</Text>
)}
</View>
<StationNumberMaker
currentStation={nextStationData}
singleSize={20}
useEach={true}
/>
<Text
style={{
fontSize: 18,
fontWeight: "bold",
color: "white",
flex: 1,
}}
>
{nextStationData[0]?.Station_JP || "不明"}
</Text>
{fixedPositionSize !== 226 && (
<View
style={{
backgroundColor: colors.background,
width: 10,
borderLeftColor: "black",
borderTopColor: "black",
borderBottomColor: colors.background,
borderRightColor: colors.background,
borderTopWidth: 21,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 7,
}}
></View>
)}
</View>
</View>
<CurrentPositionBox
train={train}
lineColor={lineColor}
trainDataWithThrough={untilStationData}
isSmall={fixedPositionSize !== 226}
/>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
borderTopColor: "black",
borderTopWidth: 2,
}}
pointerEvents="box-none"
>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
}}
onPress={() => {
setFixedPosition({ type: null, value: null });
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "black",
paddingHorizontal: 5,
height: 26,
}}
>
<Ionicons name="lock-closed" size={15} color={fixed.textOnPrimary} />
<Text
style={{
color: fixed.textOnPrimary,
fontSize: 15,
paddingRight: 5,
}}
>
</Text>
<Ionicons name="close" size={15} color={fixed.textOnPrimary} />
</View>
<View
style={{
backgroundColor: "#0000",
width: 6,
borderLeftColor: "black",
borderTopColor: "black",
borderBottomColor: "#0000",
borderRightColor: "#0000",
borderBottomWidth: 26,
borderLeftWidth: 10,
borderRightWidth: 0,
borderTopWidth: 0,
height: 26,
}}
/>
</TouchableOpacity>
<TouchableOpacity
style={{
flexDirection: "row",
alignItems: "center",
}}
onPress={() => {
LayoutAnimation.configureNext({
duration: 200,
update: { type: "easeInEaseOut", springDamping: 0.4 },
});
if (fixedPositionSize === 226) {
setFixedPositionSize(mapSwitch == "true" ? 76 : 80);
} else {
setFixedPositionSize(226);
}
}}
>
<View
style={{
backgroundColor: "#0000",
width: 6,
borderLeftColor: "#0000",
borderTopColor: "black",
borderBottomColor: "#0000",
borderRightColor: "black",
borderBottomWidth: 26,
borderLeftWidth: 0,
borderRightWidth: 10,
borderTopWidth: 0,
height: 26,
}}
/>
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: "black",
paddingHorizontal: 5,
height: 26,
}}
>
<Ionicons
name={fixedPositionSize == 226 ? "chevron-up" : "chevron-down"}
size={15}
color="white"
/>
<Text
style={{
color: "white",
paddingRight: 5,
backgroundColor: "black",
fontSize: 15,
}}
>
{fixedPositionSize == 226 ? "列車情報縮小" : "列車情報展開"}
</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
};
const CurrentPositionBox = ({
train,
lineColor,
trainDataWithThrough,
isSmall,
}) => {
const { colors } = useThemeColors();
let firstText = "";
let secondText = "";
let marginText = "";
const { isBetween, Pos: PosData } = trainPosition(train);
if (isBetween === true) {
const { from, to } = PosData;
firstText = from;
secondText = to;
marginText = "→";
} else {
const { Pos } = PosData;
if (Pos !== "") {
firstText = Pos;
}
}
const delayTime = train?.delay == "入線" ? 0 : parseInt(train?.delay);
return (
<View
style={{
flex: isSmall ? 1 : 3,
backgroundColor: colors.background,
flexDirection: "row",
}}
>
{isSmall && (
<View style={{ flexDirection: "column" }}>
<View
style={{
backgroundColor: colors.background,
width: 10,
borderLeftColor: lineColor,
borderTopColor: lineColor,
borderBottomColor: colors.background,
borderRightColor: colors.background,
borderTopWidth: 28,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 10,
}}
></View>
<View
style={{
backgroundColor: colors.background,
width: 10,
borderLeftColor: colors.background,
borderTopColor: colors.background,
borderBottomColor: colors.background,
borderRightColor: colors.background,
borderTopWidth: 18,
borderBottomWidth: 0,
borderLeftWidth: 0,
borderRightWidth: 10,
}}
></View>
</View>
)}
<ScrollView
style={{ flex: 1, flexDirection: "row" }}
horizontal
overScrollMode="always"
>
{trainDataWithThrough.length > 0 &&
trainDataWithThrough.map((d, index) => (
<EachStopData
d={d}
index={index}
key={d+"FixedTrainBoxEachStopData"}
delayTime={delayTime}
isSmall={isSmall}
secondText={secondText}
/>
))}
</ScrollView>
</View>
);
};
type eachStopType = {
d: string;
delayTime: number;
isSmall: boolean;
index: number;
secondText: string;
};
const EachStopData: FC<eachStopType> = (props) => {
const { colors } = useThemeColors();
const { d, delayTime, isSmall, index, secondText } = props;
if (!d) return null;
if (d == "") return null;
const [station, se, time] = d.split(",");
let distanceMinute = 0;
if (time != "") {
const now = dayjs();
const hour = parseInt(time.split(":")[0]);
const distanceTime = now
.hour(hour < 4 ? hour + 24 : hour)
.minute(parseInt(time.split(":")[1]));
distanceMinute = distanceTime.diff(now, "minute") + delayTime;
if (now.hour() < 4) {
if (hour < 4) {
distanceMinute = distanceMinute - 1440;
}
}
}
return (
<>
<View
style={{
flexDirection: "column",
backgroundColor: se.includes("通") ? "#6e6e6e77" : "#6e6e6eff",
borderRadius: 30,
marginHorizontal: isSmall ? 2 : 4,
marginVertical: isSmall ? 0 : 2,
padding: isSmall ? 2 : 4,
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
}}
key={d + "CurrentPositionBox"}
>
{station.split("").map((i, index, array) => {
return (
<Text
key={i + index}
style={{
fontSize:
array.length < 5 ? (isSmall ? 5 : 12) : isSmall ? 3 : 10,
color: "white",
margin: 0,
padding: 0,
fontWeight: "bold",
}}
>
{i}
</Text>
);
})}
<View style={{ flex: 1 }} />
{isSmall ||
(time != "" && (
<Text
style={{
fontSize: isSmall ? 8 : 12,
color: colors.text,
backgroundColor: colors.background,
fontWeight: "bold",
}}
>
{distanceMinute}
</Text>
))}
<Text
style={{
fontSize: isSmall ? 8 : 14,
color:
index == 1 && secondText == ""
? "#ffe852ff"
: se.includes("通")
? "#020202ff"
: "white",
marginTop: isSmall ? 0 : 3,
height: isSmall ? "auto" : 17,
fontWeight: "bold",
}}
>
{index == 1 && secondText == ""
? "→"
: se.includes("通")
? null
: "●"}
</Text>
</View>
{index == 0 && secondText != "" && (
<View
style={{
flexDirection: "column",
backgroundColor: "#0000",
borderRadius: 10,
marginHorizontal: isSmall ? 2 : 4,
padding: isSmall ? 2 : 4,
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
}}
>
<View style={{ flex: 1 }} />
<Ionicons
name="arrow-forward"
size={isSmall ? 8 : 14}
color={colors.icon}
style={{ marginTop: isSmall ? 0 : 3 }}
/>
</View>
)}
</>
);
};