- 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>
1056 lines
34 KiB
TypeScript
1056 lines
34 KiB
TypeScript
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>
|
||
)}
|
||
</>
|
||
);
|
||
};
|