Merge commit '01378c2f7e3004ecd500d6611117af4d343335c2' into feature/position-tracking-system

This commit is contained in:
harukin-expo-dev-env
2025-09-04 21:03:59 +00:00
12 changed files with 1518 additions and 19 deletions

View File

@@ -19,6 +19,7 @@ import { useNavigation } from "@react-navigation/native";
import { news } from "./config/newsUpdate";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import GeneralWebView from "./GeneralWebView";
import { StationDiagramView } from "@/components/StationDiagram/StationDiagramView";
const Stack = createStackNavigator();
export function MenuPage() {
@@ -86,7 +87,7 @@ export function MenuPage() {
})
.catch((error) => {
if (__DEV__) {
console.warn('お気に入り駅の読み込みに失敗しました:', error);
console.warn("お気に入り駅の読み込みに失敗しました:", error);
}
});
});
@@ -112,6 +113,11 @@ export function MenuPage() {
/>
)}
/>
<Stack.Screen
name="stDiagram"
options={{ ...optionData, gestureEnabled: false }}
component={StationDiagramView}
/>
<Stack.Screen name="news" options={optionData} component={News} />
<Stack.Screen
name="setting"
@@ -133,7 +139,11 @@ export function MenuPage() {
component={AllTrainDiagramView}
/>
<Stack.Screen name="howto" options={optionData} component={HowTo} />
<Stack.Screen name="generalWebView" options={optionData} component={GeneralWebView} />
<Stack.Screen
name="generalWebView"
options={optionData}
component={GeneralWebView}
/>
</Stack.Navigator>
);
}

15
Top.js
View File

@@ -14,6 +14,7 @@ import { AS } from "./storageControl";
import { news } from "./config/newsUpdate";
import { Linking, Platform } from "react-native";
import GeneralWebView from "./GeneralWebView";
import { StationDiagramView } from "@/components/StationDiagram/StationDiagramView";
const Stack = createStackNavigator();
export const Top = () => {
const { webview } = useCurrentTrain();
@@ -37,7 +38,8 @@ export const Top = () => {
return;
}
if (!isFocused()) navigate("positions", { screen: "Apps" });
else if (mapSwitch == "true") navigate("positions", { screen: "trainMenu" });
else if (mapSwitch == "true")
navigate("positions", { screen: "trainMenu" });
else webview.current?.injectJavaScript(`AccordionClassEvent()`);
return;
};
@@ -64,8 +66,17 @@ export const Top = () => {
options={{ ...optionData }}
component={TrainBase}
/>
<Stack.Screen
name="stDiagram"
options={{ ...optionData, gestureEnabled: false }}
component={StationDiagramView}
/>
<Stack.Screen name="howto" options={optionData} component={HowTo} />
<Stack.Screen name="generalWebView" options={optionData} component={GeneralWebView} />
<Stack.Screen
name="generalWebView"
options={optionData}
component={GeneralWebView}
/>
<Stack.Screen name="news" options={optionData} component={News} />
<Stack.Screen
name="trainMenu"

View File

@@ -19,6 +19,8 @@ import { 駅構内図 } from "./StationDeteilView/StationInsideMapButton";
import { WebSiteButton } from "./StationDeteilView/WebSiteButton";
import { StationTimeTableButton } from "./StationDeteilView/StationTimeTableButton";
import { StationTrainPositionButton } from "./StationDeteilView/StationTrainPositionButton";
import { StationDiagramButton } from "./StationDeteilView/StationDiagramButton";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
export const StationDeteilView = (props) => {
if (!props.payload) return <></>;
@@ -26,6 +28,7 @@ export const StationDeteilView = (props) => {
const { width } = useWindowDimensions();
const { busAndTrainData } = useBusAndTrainData();
const [trainBus, setTrainBus] = useState();
const { updatePermission } = useTrainMenu();
useEffect(() => {
if (!currentStation) return () => {};
@@ -132,6 +135,11 @@ export const StationDeteilView = (props) => {
onExit={onExit}
/>
)}
{updatePermission &&<StationDiagramButton
navigate={navigate}
onExit={onExit}
currentStation={currentStation}
/>}
{!currentStation[0].StationTimeTable || (
<StationTimeTableButton
info={info}

View File

@@ -0,0 +1,40 @@
import React, { FC } from "react";
import { Linking } from "react-native";
import { FontAwesome } from "@expo/vector-icons";
import { TicketBox } from "@/components/atom/TicketBox";
type Props = {
navigate: (screen: string, params?: object) => void;
onExit: () => void;
currentStation: {
Station_JP: string;
Station_EN: string;
StationName?: string;
MyStation?: string;
StationNumber: string;
DispNum?: string;
StationTimeTable: string;
StationMap?: string;
JrHpUrl?: string;
lat: number;
lng: number;
jslodApi: string;
}[];
};
export const StationDiagramButton: FC<Props> = (props) => {
const { navigate, onExit, currentStation } = props;
return (
<TicketBox
backgroundColor={"#8F5902"}
icon={<FontAwesome name="table" color="white" size={50} />}
flex={1}
onPressButton={() => {
navigate("stDiagram", {
currentStation,
});
onExit();
}}
>
v2
</TicketBox>
);
};

View File

@@ -0,0 +1,355 @@
import { FC, useRef, useState, useCallback, useEffect } from "react";
import {
View,
Text,
ScrollView,
useWindowDimensions,
Vibration,
} from "react-native";
import { ExGridViewItem } from "./ExGridViewItem";
import Animated, {
useAnimatedStyle,
useSharedValue,
runOnJS,
useAnimatedScrollHandler,
withTiming,
Easing,
FadeIn,
FadeOut,
BounceInUp,
FadeInUp,
FadeOutUp,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { ExGridViewTimePositionItem } from "./ExGridViewTimePositionItem";
import { useCurrentTrain } from "@/stateBox/useCurrentTrain";
import dayjs from "dayjs";
type hoge = {
trainNumber: string;
array: string;
name: string;
timeType: string;
time: string;
}[];
export const ExGridView: FC<{
data: hoge;
}> = ({ data }) => {
const groupedData: {
[d: number]: {
trainNumber: string;
array: string;
name: string;
timeType: string;
time: string;
isOperating: boolean;
}[];
} = {
"4": [],
"5": [],
"6": [],
"7": [],
"8": [],
"9": [],
"10": [],
"11": [],
"12": [],
"13": [],
"14": [],
"15": [],
"16": [],
"17": [],
"18": [],
"19": [],
"20": [],
"21": [],
"22": [],
"23": [],
"0": [],
"1": [],
"2": [],
"3": [],
};
const groupKeys = [
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"0",
"1",
"2",
"3",
];
const { width } = useWindowDimensions();
const { currentTrain } = useCurrentTrain();
data.forEach((item) => {
let isOperating = false;
let [hour, minute] = dayjs()
.hour(parseInt(item.time.split(":")[0]))
.minute(parseInt(item.time.split(":")[1]))
.format("H:m")
.split(":");
if (currentTrain.findIndex((x) => x.num == item.trainNumber) != -1) {
const currentTrainTime = currentTrain.find(
(x) => x.num == item.trainNumber
)?.delay;
if (currentTrainTime != "入線") {
[hour, minute] = dayjs()
.hour(parseInt(hour))
.minute(parseInt(minute))
.add(parseInt(currentTrainTime), "minute")
.format("H:m")
.split(":");
}
isOperating = true;
}
groupedData[hour].push({ ...item, time: `${hour}:${minute}`, isOperating });
});
// ドラッグ位置を保持する共有値
const widthX = useSharedValue(width);
const savedWidthX = useSharedValue(width);
const isChanging = useSharedValue(false);
const [scrollEnabled, setScrollEnabled] = useState(true);
const scrollRef = useRef<Animated.ScrollView>(null);
const scrollRef2 = useRef<Animated.ScrollView>(null);
// ScrollViewの有効/無効を切り替える関数
const toggleScrollEnabled = useCallback((enabled: boolean) => {
setScrollEnabled(enabled);
}, []);
// パンジェスチャー(ドラッグ)のハンドラー
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
const calc = savedWidthX.value * e.scale;
widthX.value = calc > width ? calc : width;
//runOnJS(scrollToRightEnd)();
})
.onEnd(() => {
savedWidthX.value = widthX.value;
});
const gesture = Gesture.Pan()
.minPointers(2) // 最低2本指
.maxPointers(2) // 最大2本指
.onStart(() => {
runOnJS(toggleScrollEnabled)(false);
})
.onEnd(() => {
runOnJS(toggleScrollEnabled)(true);
savedWidthX.value = widthX.value;
});
const longPressGesture = Gesture.Pan()
.minPointers(1)
.maxPointers(1)
.activateAfterLongPress(200)
.onStart(() => {
runOnJS(Vibration.vibrate)(30);
isChanging.value = true;
})
.onUpdate((e) => {
const calc = widthX.value + e.velocityY;
widthX.value = calc > width ? calc : width;
})
.onEnd(() => {
console.log("Long press ended");
isChanging.value = false;
});
// ジェスチャーを組み合わせる
const composed = Gesture.Simultaneous(
longPressGesture,
pinchGesture,
gesture
);
// アニメーションスタイル
const animatedStyle = useAnimatedStyle(() => ({
width: widthX.value,
backgroundColor: isChanging.value ? "#8adeffff" : "white",
}));
// 時ヘッダーを横にスクロールしたときの処理
const scrollX = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollX.value = event.contentOffset.x;
},
});
const stickyTextStyle = useAnimatedStyle(() => ({
transform: [{ translateX: scrollX.value }],
}));
const animatedLongPressStyle = useAnimatedStyle(() => ({
display: isChanging.value ? "flex" : "none",
}));
useEffect(() => {
const getCurrentTime = dayjs().hour();
setTimeout(() => {
const keyTime =
getCurrentTime - 4 <= 0 ? getCurrentTime + 24 : getCurrentTime;
const goTo = keyTime * 60;
if (goTo > 400) {
scrollRef2.current?.scrollTo({ y: goTo - 300, animated: true });
}
}, 400);
}, [scrollRef2]);
return (
<>
<Animated.View
style={[
{
position: "absolute",
width,
backgroundColor: "#26d1baff",
zIndex: 500,
top: 0,
},
animatedLongPressStyle,
]}
entering={FadeInUp}
exiting={FadeOutUp}
>
<Text style={{ fontSize: 30, textAlign: "center", flex: 1 }}>
  
</Text>
</Animated.View>
<GestureDetector gesture={composed}>
<Animated.ScrollView
horizontal
nestedScrollEnabled
pinchGestureEnabled={false}
scrollEnabled={scrollEnabled}
onScroll={scrollHandler}
onContentSizeChange={(contentWidth) => {
// 現在のスクロール位置を取得
const currentScrollX = scrollX.value;
const containerWidth = width - 50;
// コンテンツが画面からはみ出している場合のみ右端にスクロール
if (currentScrollX + containerWidth > contentWidth) {
const newScrollX = Math.max(0, contentWidth - containerWidth);
scrollRef.current?.scrollTo({ x: newScrollX, animated: true });
}
}}
ref={scrollRef}
contentContainerStyle={{
flexDirection: "column",
backgroundColor: "white",
}}
>
<Animated.View
style={[
{
width: width,
flexDirection: "row",
},
animatedStyle,
]}
>
{Array.from({ length: 60 }, (_, i) => i + 1).map((num) => {
if (num % 5 === 0) {
return (
<Text
key={num}
style={{
flex: 1,
textAlign: "left",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
fontSize: 12,
}}
>
{num - 5}
</Text>
);
} else return <></>;
})}
<Text
style={{
textAlign: "right",
borderRightWidth: 0.5,
borderColor: "#ccc",
flexWrap: "nowrap",
fontSize: 12,
width: 50,
}}
>
()
</Text>
</Animated.View>
<Animated.ScrollView
style={[{ width: width }, animatedStyle]}
pinchGestureEnabled={false}
minimumZoomScale={0.5}
maximumZoomScale={3.0}
scrollEnabled={scrollEnabled}
stickyHeaderIndices={
groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : []
}
ref={scrollRef2}
>
{groupKeys.map((hour) => [
<View
style={{
padding: 5,
borderBottomWidth: 0.5,
borderTopWidth: 0.5,
borderBottomColor: "#ccc",
backgroundColor: "#f0f0f0",
}}
key={hour}
>
<Animated.Text
style={[
{
fontSize: 15,
zIndex: 1,
marginLeft: 0,
},
stickyTextStyle,
]}
>
{hour}
</Animated.Text>
</View>,
<View
style={{
flexDirection: "row",
position: "relative",
height: 50,
}}
>
{groupedData[hour].map((d, i, array) => (
<ExGridViewItem
key={d.trainNumber + i}
d={d}
index={i}
width={widthX}
array={array}
/>
))}
<ExGridViewTimePositionItem width={widthX} hour={hour} />
</View>,
])}
</Animated.ScrollView>
</Animated.ScrollView>
</GestureDetector>
</>
);
};

View File

@@ -0,0 +1,268 @@
import { migrateTrainName } from "@/lib/eachTrainInfoCoreLib/migrateTrainName";
import { getStringConfig, typeID } from "@/lib/getStringConfig";
import { getTrainType } from "@/lib/getTrainType";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { FC, useEffect, useLayoutEffect, useMemo, useState } from "react";
import {
View,
Text,
TouchableOpacity,
useWindowDimensions,
} from "react-native";
import { customTrainDataDetector } from "../custom-train-data";
import dayjs from "dayjs";
import { SheetManager } from "react-native-actions-sheet";
import { useNavigation } from "@react-navigation/native";
import { lineList } from "@/lib/getStationList";
import { useStationList } from "@/stateBox/useStationList";
import { SharedValue, useAnimatedStyle } from "react-native-reanimated";
import Animated from "react-native-reanimated";
import lineColorList from "@/assets/originData/lineColorList";
export const ExGridViewItem: FC<{
d: {
trainNumber: string;
array: string;
name: string;
timeType: string;
time: string;
isOperating: boolean;
};
index: number;
width: SharedValue<number>;
array: {
train: string;
lastStation: string;
time: string;
isThrough?: boolean;
}[];
}> = ({ d, index, width, array }) => {
const { allCustomTrainData } = useAllTrainDiagram();
const { originalStationList, stationList } = useStationList();
const { navigate, goBack } = useNavigation();
const [trainData, setTrainData] = useState<{
ToData: string;
TrainNumber: string;
id: string;
img: string;
info?: string;
infoUrl: string;
infogram: string;
isEdit: boolean;
isSeason: boolean;
trainName: string;
trainNumDistance?: number;
type: typeID;
viaData?: string;
uwasa?: string;
}>();
useEffect(() => {
if (allCustomTrainData) {
allCustomTrainData.forEach((x) => {
if (x.TrainNumber === d.trainNumber) {
setTrainData(x);
}
});
}
}, []);
const { color, name, data } = getTrainType(trainData?.type, true);
// 列車名、種別、フォントの取得
const [
typeString,
trainName,
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
] = useMemo(() => {
const {
type,
trainName,
trainNumDistance,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
} = customTrainDataDetector(d.trainNumber, allCustomTrainData);
const [typeString, fontAvailable, isOneMan] = getStringConfig(
type,
d.trainNumber
);
const trainData = d.array.split("#").filter((d) => d !== "");
switch (true) {
case trainData[trainData.length - 1] === undefined:
return [
typeString,
"",
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
];
default:
// 行先がある場合は、行先を取得
const trainName = (d.timeType == "着" || d.timeType == "着編") ? trainData[0].split(",")[0] : trainData[trainData.length - 1].split(",")[0]
return [
typeString,
migrateTrainName(trainName),
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
];
}
}, [d.array]);
const timeArray = d.time.split(":").map((s) => parseInt(s));
const formattedTime = dayjs()
.set("hour", timeArray[0])
.set("minute", timeArray[1])
.format("m");
let isSameTimeBefore = false;
if (index > 0) {
const beforeItem = array[index - 1];
const beforeTimeArray = beforeItem.time.split(":").map((s) => parseInt(s));
const beforeFormattedTime = dayjs()
.set("hour", beforeTimeArray[0])
.set("minute", beforeTimeArray[1])
.format("m");
isSameTimeBefore = beforeFormattedTime === formattedTime;
}
const openStationACFromEachTrainInfo = async (stationName) => {
await SheetManager.hide("EachTrainInfo");
const findStationEachLine = (selectLine) => {
let NearStation = selectLine.filter((d) => d.Station_JP == stationName);
return NearStation;
};
let returnDataBase = lineList
.map((d) => findStationEachLine(originalStationList[d]))
.filter((d) => d.length > 0)
.reduce((pre, current) => {
pre.push(...current);
return pre;
}, []);
if (returnDataBase.length) {
const payload = {
currentStation: returnDataBase,
navigate,
//@ts-ignore
useShow: () => SheetManager.show("StationDetailView", { payload }),
onExit: () => SheetManager.hide("StationDetailView"),
}; //@ts-ignore
setTimeout(() => SheetManager.show("StationDetailView", { payload }), 50);
} else {
SheetManager.hide("StationDetailView");
}
};
const openTrainInfo = () => {
let TrainNumber = "";
if (trainData.trainNumDistance != undefined) {
const timeInfo =
parseInt(trainData.TrainNumber.replace("M", "").replace("D", "")) -
trainData.trainNumDistance;
TrainNumber = timeInfo + "号";
}
const payload = {
data: {
trainNum: trainData.TrainNumber,
limited: `${data}:${trainData.trainName}${TrainNumber}`,
},
navigate,
openStationACFromEachTrainInfo,
from: d.isOperating ? null :"AllTrainIDList",
};
SheetManager.show("EachTrainInfo", {
//@ts-ignore
payload,
onClose: (data) => {
//alert(data);
},
});
};
const [stationColor, setStationColor] = useState(["gray"]);
useEffect(() => {
const Stations = stationList
.map((a) => a.filter((d) => d.StationName == trainName))
.reduce((newArray, e) => newArray.concat(e), []);
const StationNumbers =
Stations &&
Stations.filter((d) => d.StationNumber).map((d) => d.StationNumber);
if (StationNumbers) {
const stationLineColor = StationNumbers.map(
(d) => lineColorList[d.charAt(0)]
);
setStationColor(stationLineColor || ["gray"]);
}
}, [stationList]);
// if(typeString == "回送"){
// return<></>;
// }
const animatedStyle = useAnimatedStyle(() => {
const leftPosition =
((((width.value - 50) / 100) * parseInt(formattedTime)) / 60) * 100;
return {
left: leftPosition,
};
}, [formattedTime]);
return (
<View style={{ left: 0, height: 50 }}>
<Animated.View
style={[
{
flexDirection: "column",
//borderTopWidth: 1,
//borderBottomWidth: 0.5,
borderStyle: "solid",
borderColor: "darkgray",
opacity: d.timeType.includes("通") ? 0.5 : 1,
position: "absolute",
height: "100%",
width: 28,
top: isSameTimeBefore ? 10 : 0,
},
animatedStyle,
]}
>
<TouchableOpacity style={{ flex: 1 }} onPress={() => openTrainInfo()}>
<View style={{ position: "relative" }}>
<Text style={{ fontSize: 20, color: color, opacity: isSameTimeBefore ? 0 : 1, fontWeight:d.isOperating ? "bold" : "thin", fontStyle:d.isOperating? "italic" :"normal" }}>{formattedTime}</Text>
<Text
style={{
fontSize: 10,
position: "absolute",
bottom: 0,
right: 0,
fontWeight: "bold",
}}
>
{d.timeType}
</Text>
</View>
<View style={{ flex: 1, flexDirection: "column" }}>
<Text
style={{
fontSize: 8,
flex: 1,
fontWeight: "bold",
color: stationColor[0],
}}
>
{trainName}
</Text>
</View>
</TouchableOpacity>
</Animated.View>
</View>
);
};

View File

@@ -0,0 +1,44 @@
import { FC } from "react";
import { View } from "react-native";
import dayjs from "dayjs";
import { SharedValue, useAnimatedStyle } from "react-native-reanimated";
import Animated from "react-native-reanimated";
export const ExGridViewTimePositionItem: FC<{
width: SharedValue<number>;
hour: string;
}> = ({ width, hour }) => {
const date = dayjs();
const formattedTime = date.format("m");
const formattedHour = date.format("H");
// if(typeString == "回送"){
// return<></>;
// }
const animatedStyle = useAnimatedStyle(() => {
const leftPosition =
((((width.value - 50) / 100) * parseInt(formattedTime)) / 60) * 100;
return {
left: leftPosition,
};
}, [formattedTime]);
if (formattedHour != hour) return <></>;
return (
<View style={{ left: 0, height: 50, width: 1 }}>
<Animated.View
style={[
{
flexDirection: "column",
borderLeftWidth: 2,
//borderBottomWidth: 0.5,
borderStyle: "solid",
borderColor: "red",
position: "absolute",
height: "100%",
},
animatedStyle,
]}
/>
</View>
);
};

View File

@@ -0,0 +1,43 @@
import { FC } from "react";
import { ListViewItem } from "@/components/StationDiagram/ListViewItem";
import { View, Text, ScrollView } from "react-native";
export const ListView: FC<{
data: {
trainNumber: string;
array: string;
name: string;
type: string;
time: string;
}[];
}> = ({ data }) => {
const groupedData = {};
const groupKeys = [];
data.forEach((item) => {
const hour = item.time.split(":")[0];
if (!groupedData[hour]) {
groupedData[hour] = [];
groupKeys.push(hour);
}
groupedData[hour].push(item);
});
return (
<ScrollView
style={{ backgroundColor: "white" }}
stickyHeaderIndices={
groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : []
}
>
{groupKeys.map((hour) => [
<View style={{ backgroundColor: "white", padding: 5, borderBottomWidth: 0.5, borderTopWidth: 0.5, borderBottomColor: "#ccc" }} key={hour}>
<Text style={{ fontSize: 15 }}>{hour}</Text>
</View>,
<View>
{groupedData[hour].map((d, i) => (
<ListViewItem key={d.trainNumber + i} d={d} />
))}
</View>,
])}
</ScrollView>
);
};

View File

@@ -0,0 +1,244 @@
import { migrateTrainName } from "@/lib/eachTrainInfoCoreLib/migrateTrainName";
import { getStringConfig, typeID } from "@/lib/getStringConfig";
import { getTrainType } from "@/lib/getTrainType";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { FC, useEffect, useMemo, useState } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { customTrainDataDetector } from "../custom-train-data";
import dayjs from "dayjs";
import { SheetManager } from "react-native-actions-sheet";
import { useNavigation } from "@react-navigation/native";
import { lineList } from "@/lib/getStationList";
import { useStationList } from "@/stateBox/useStationList";
export const ListViewItem: FC<{
d: {
trainNumber: string;
array: string;
name: string;
type: string;
time: string;
};
}> = ({ d }) => {
const { allCustomTrainData } = useAllTrainDiagram();
const { navigate, goBack } = useNavigation();
const [trainData, setTrainData] = useState<{
ToData: string;
TrainNumber: string;
id: string;
img: string;
info?: string;
infoUrl: string;
infogram: string;
isEdit: boolean;
isSeason: boolean;
trainName: string;
trainNumDistance?: number;
type: typeID;
viaData?: string;
uwasa?: string;
}>();
useEffect(() => {
if (allCustomTrainData) {
allCustomTrainData.forEach((x) => {
if (x.TrainNumber === d.trainNumber) {
setTrainData(x);
}
});
}
}, []);
const { color, name, data } = getTrainType(trainData?.type, true);
const { originalStationList } = useStationList();
// 列車名、種別、フォントの取得
const [
typeString,
trainName,
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
] = useMemo(() => {
const {
type,
trainName,
trainNumDistance,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
} = customTrainDataDetector(d.trainNumber, allCustomTrainData);
const [typeString, fontAvailable, isOneMan] = getStringConfig(
type,
d.trainNumber
);
const trainData = d.array.split("#").filter((d) => d !== "");
switch (true) {
case trainData[trainData.length - 1] === undefined:
return [
typeString,
"",
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
];
default:
// 行先がある場合は、行先を取得
return [
typeString,
migrateTrainName(
trainData[trainData.length - 1].split(",")[0] + "行き"
),
fontAvailable,
isOneMan,
infogram,
isEdit,
uwasa,
vehicleFormation,
trainInfoUrl,
];
}
}, [d.array]);
const timeArray = d.time.split(":").map((s) => parseInt(s));
const formattedTime = dayjs()
.set("hour", timeArray[0])
.set("minute", timeArray[1])
.format("HH:mm");
const openStationACFromEachTrainInfo = async (stationName) => {
await SheetManager.hide("EachTrainInfo");
const findStationEachLine = (selectLine) => {
let NearStation = selectLine.filter((d) => d.Station_JP == stationName);
return NearStation;
};
let returnDataBase = lineList
.map((d) => findStationEachLine(originalStationList[d]))
.filter((d) => d.length > 0)
.reduce((pre, current) => {
pre.push(...current);
return pre;
}, []);
if (returnDataBase.length) {
const payload = {
currentStation: returnDataBase,
navigate,
//@ts-ignore
useShow: () => SheetManager.show("StationDetailView", { payload }),
onExit: () => SheetManager.hide("StationDetailView"),
};//@ts-ignore
setTimeout(() => SheetManager.show("StationDetailView", { payload }), 50);
} else {
SheetManager.hide("StationDetailView");
}
};
const openTrainInfo = () => {
let TrainNumber = "";
if (trainData.trainNumDistance != undefined) {
const timeInfo =
parseInt(trainData.TrainNumber.replace("M", "").replace("D", "")) -
trainData.trainNumDistance;
TrainNumber = timeInfo + "号";
}
const payload = {
data: {
trainNum: trainData.TrainNumber,
limited: `${data}:${trainData.trainName}${TrainNumber}`,
},
navigate,
openStationACFromEachTrainInfo,
from: "AllTrainIDList",
};
SheetManager.show("EachTrainInfo", {
//@ts-ignore
payload,
onClose: (data) => {
//alert(data);
},
});
};
return (
<TouchableOpacity
style={{
flexDirection: "row",
marginHorizontal: 10,
borderTopWidth: 1,
borderBottomWidth: 0.5,
borderStyle: "solid",
borderColor: "darkgray",
padding: 10,
opacity: d.type.includes("通") ? 0.5 : 1,
}}
onPress={() => openTrainInfo()}
>
<View style={{ position: "relative" }}>
<Text style={{ fontSize: 30 }}>{formattedTime}</Text>
<Text
style={{
fontSize: 10,
position: "absolute",
bottom: -3,
right: 0,
fontWeight: "bold",
}}
>
{d.type}
</Text>
</View>
<View style={{ flex: 1, flexDirection: "column" }}>
<View style={{ flexDirection: "row" }}>
<Text
style={{
fontSize: 15,
fontFamily: fontAvailable ? "JR-Nishi" : undefined,
fontWeight: !fontAvailable ? "bold" : undefined,
paddingTop: fontAvailable ? 2 : 0,
paddingLeft: 10,
color: color,
}}
>
{typeString}
</Text>
<Text
style={{
fontSize: 15,
fontWeight: "bold",
flex: 1,
paddingLeft: 2,
color: color,
}}
>
{trainData?.trainName +
(trainData?.trainNumDistance !== null
? ` ${parseInt(d.trainNumber) - trainData?.trainNumDistance}`
: "")}
</Text>
<Text
style={{
fontSize: 15,
fontWeight: "bold",
}}
>
{trainData?.TrainNumber}
</Text>
</View>
<Text
style={{
fontSize: 15,
flex: 1,
paddingHorizontal: 10,
fontWeight: "bold",
}}
>
{trainName}
</Text>
</View>
</TouchableOpacity>
);
};

View File

@@ -0,0 +1,454 @@
import { FC, useEffect, useState } from "react";
import {
View,
Text,
ScrollView,
TextInput,
Keyboard,
KeyboardAvoidingView,
Platform,
TouchableOpacity,
LayoutAnimation,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
import { BigButton } from "../atom/BigButton";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { ListView } from "@/components/StationDiagram/ListView";
import dayjs from "dayjs";
import { ExGridView } from "./ExGridView";
import { Switch } from "react-native-elements";
import { customTrainDataDetector } from "../custom-train-data";
import { typeID } from "@/lib/getStringConfig";
import { colorString } from "@/lib/getTrainType";
type props = {
route: {
params: {
currentStation: {
Station_JP: string;
Station_EN: string;
StationName?: string;
MyStation?: string;
StationNumber: string;
DispNum?: string;
StationTimeTable: string;
StationMap?: string;
JrHpUrl?: string;
lat: number;
lng: number;
jslodApi: string;
}[];
};
};
};
export const StationDiagramView: FC<props> = ({ route }) => {
if (!route.params) {
return null;
}
const { currentStation } = route.params;
// 必要な情報:駅情報、全ダイヤ、カスタム列車情報
// 表示モード:縦並びリスト、横並びグリッド(時刻分割)、横並び単純左詰め
// フィルタリング:終点路線、種別、行先、関係停車駅
const { keyList, allTrainDiagram, allCustomTrainData } = useAllTrainDiagram();
const { navigate, addListener, goBack, canGoBack } = useNavigation();
const [keyBoardVisible, setKeyBoardVisible] = useState(false);
const [input, setInput] = useState("");
const [selectedTypeList, setSelectedTypeList] = useState<typeID[]>([
"Normal",
"OneMan",
"Rapid",
"OneManRapid",
"LTDEXP",
"NightLTDEXP",
]);
type hoge = {
trainNumber: string;
array: string;
name: string;
timeType: string;
time: string;
}[];
const [showTypeFiltering, setShowTypeFiltering] = useState(false);
const [showLastStop, setShowLastStop] = useState(false);
const [threw, setIsThrew] = useState(false);
const [currentStationDiagram, setCurrentStationDiagram] = useState<hoge>([]);
useEffect(() => {
if (allTrainDiagram && currentStation.length > 0) {
const stationName = currentStation[0].Station_JP;
let returnDataArray: hoge = [];
keyList
.filter((s) => {
const boolData = allTrainDiagram[s];
let isStop = false;
let isInput = false;
boolData.split("#").forEach((d) => {
const [station, type, time] = d.split(",");
if (station === stationName) isStop = true;
if (station === input && type && !type.includes("通"))
isInput = true;
});
if (input && input.length > 0) {
return isInput && isStop;
}
return isStop;
})
.forEach((d) => {
allTrainDiagram[d]
.split("#")
.filter((d) => {
const [station, type, time] = d.split(",");
return station === stationName;
})
.forEach((x) => {
const [name, timeType, time] = x.split(",");
if (!name || !timeType || !time) return;
const { img, trainName, type, trainNumDistance, infogram } =
customTrainDataDetector(d, allCustomTrainData);
const arrayData = {
trainNumber: d,
array: allTrainDiagram[d],
name,
timeType,
time,
};
// //条件によってフィルタリング
if (!threw && timeType && timeType.includes("通")) return;
if (!showLastStop && timeType && timeType.includes("着")) return;
if (
selectedTypeList.length > 0 &&
selectedTypeList.findIndex((item) => item === type) === -1
) {
if (
selectedTypeList.findIndex(
(item) => item === "Forwarding"
) !== -1
) {
if (!d.match(/[A,B,R,H,E,T,L]/)) return;
} else if (
selectedTypeList.findIndex((item) => item === "SPCL") !== -1
) {
if (!d.match(/9\d\d\d[D,M,S]/)) return;
} else {
return;
}
}
returnDataArray.push(arrayData);
});
});
setCurrentStationDiagram(
returnDataArray.sort((a, b) => {
const adjustTime = (t: string) => {
const [h, m] = t.split(":").map(Number);
// 4時未満は翌日の時刻とみなして+24時間
return h < 4
? dayjs().add(1, "day").hour(h).minute(m)
: dayjs().hour(h).minute(m);
};
const aa = adjustTime(a.time);
const bb = adjustTime(b.time);
const x = aa.isAfter(bb);
return x ? 1 : -1;
//return true;
})
);
}
}, [currentStation, showLastStop, threw, input, selectedTypeList]);
useEffect(() => {
const showSubscription = Keyboard.addListener("keyboardDidShow", () => {
setKeyBoardVisible(true);
});
const hideSubscription = Keyboard.addListener("keyboardDidHide", () => {
setKeyBoardVisible(false);
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, []);
return (
<View style={{ height: "100%", backgroundColor: "#0099CC" }}>
<Text
style={{
textAlign: "center",
fontSize: 20,
color: "white",
fontWeight: "bold",
paddingVertical: 10,
}}
>
{currentStation[0].Station_JP}
</Text>
{/* <ListView data={currentStationDiagram} /> */}
<ExGridView data={currentStationDiagram} />
{/* <Text
style={{
backgroundColor: "white",
borderWidth: 1,
borderStyle: "solid",
}}
>
お気に入り登録した駅のうち、位置情報システムで移動可能な駅が表示されています。タップすることで位置情報システムの当該の駅に移動します。
</Text> */}
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={80}
enabled={Platform.OS === "ios"}
>
<ScrollView horizontal style={{ height: 35, flexDirection: "row" }}>
<TouchableOpacity
style={{
alignItems: "center",
marginHorizontal: 5,
backgroundColor: threw ? "white" : "#ffffff00",
alignSelf: "center",
borderColor: "white",
borderWidth: 1,
borderRadius: 100,
}}
onPress={() => {
setIsThrew(!threw);
}}
>
<Text
style={{
color: threw ? "#0099CC" : "white",
fontSize: 14,
margin: 5,
}}
>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
alignItems: "center",
marginHorizontal: 5,
backgroundColor: showLastStop ? "white" : "#ffffff00",
alignSelf: "center",
borderColor: "white",
borderWidth: 1,
borderRadius: 100,
}}
onPress={() => {
setShowLastStop(!showLastStop);
}}
>
<Text
style={{
color: showLastStop ? "#0099CC" : "white",
fontSize: 14,
margin: 5,
}}
>
</Text>
</TouchableOpacity>
<View
style={{
height: "auto",
borderLeftWidth: 1,
margin: 5,
borderColor: "white",
}}
/>
{showTypeFiltering ? (
<>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="普通"
typeID="Normal"
color="black"
relativeID={["OneMan"]}
/>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="快速"
typeID="Rapid"
color="#00b8d8cc"
relativeID={["OneManRapid"]}
/>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="特急"
typeID="LTDEXP"
color="red"
relativeID={["NightLTDEXP"]}
/>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="臨時"
typeID="SPCL"
color="#297bff"
relativeID={["SPCL_Normal", "SPCL_Rapid", "SPCL_EXP", "Party"]}
/>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="貨物"
typeID="Freight"
color="#00869ecc"
/>
<TypeSelectorBox
selectedTypeList={selectedTypeList}
setSelectedTypeList={setSelectedTypeList}
typeName="回送"
typeID="Forwarding"
color="#727272cc"
relativeID={["FreightForwarding"]}
/>
<TouchableOpacity
style={{
alignItems: "center",
marginHorizontal: 5,
backgroundColor: "#ffffff00",
alignSelf: "center",
borderColor: "white",
borderWidth: 1,
borderRadius: 100,
}}
onPress={() => {
LayoutAnimation.configureNext(
LayoutAnimation.Presets.easeInEaseOut
);
setShowTypeFiltering(false);
}}
>
<Text
style={{
color: "white",
fontSize: 14,
margin: 5,
}}
>
</Text>
</TouchableOpacity>
</>
) : (
<TouchableOpacity
style={{
alignItems: "center",
marginHorizontal: 5,
backgroundColor: "#ffffff00",
alignSelf: "center",
borderColor: "white",
borderWidth: 1,
borderRadius: 100,
}}
onPress={() => {
LayoutAnimation.configureNext(
LayoutAnimation.Presets.easeInEaseOut
);
setShowTypeFiltering(true);
}}
>
<Text
style={{
color: "white",
fontSize: 14,
margin: 5,
}}
>
</Text>
</TouchableOpacity>
)}
</ScrollView>
<View
style={{
height: 35,
margin: 5,
alignItems: "center",
backgroundColor: "#F4F4F4",
flexDirection: "row",
paddingLeft: 10,
paddingRight: 10,
borderRadius: 25,
borderColor: "#F4F4F4",
}}
>
<TextInput
placeholder="駅名を入力して停車駅でフィルタリングします。"
onFocus={() => setKeyBoardVisible(true)}
onEndEditing={() => {}}
onChange={(ret) => setInput(ret.nativeEvent.text)}
value={input}
style={{ flex: 1 }}
/>
</View>
</KeyboardAvoidingView>
{keyBoardVisible || (
<BigButton onPress={() => goBack()} string="閉じる" />
)}
</View>
);
};
export const TypeSelectorBox: FC<{
selectedTypeList: typeID[];
setSelectedTypeList: (list: typeID[]) => void;
typeName: string;
typeID: typeID;
color: colorString;
relativeID?: typeID[];
}> = (props) => {
const {
selectedTypeList,
setSelectedTypeList,
typeName,
typeID,
relativeID,
color,
} = props;
const isSelected =
selectedTypeList.findIndex((item) => item === typeID) !== -1;
return (
<TouchableOpacity
style={{
alignItems: "center",
marginHorizontal: 5,
opacity: isSelected ? 1 : 0.8,
backgroundColor: isSelected ? "white" : color,
alignSelf: "center",
borderColor: color,
borderWidth: 1,
borderRadius: 100,
}}
onPress={() => {
if (selectedTypeList.findIndex((item) => item === typeID) === -1) {
setSelectedTypeList([
...selectedTypeList,
typeID,
...(relativeID ?? []),
]);
} else {
setSelectedTypeList(
selectedTypeList.filter(
(item) => item !== typeID && !relativeID?.includes(item)
)
);
}
}}
>
<Text
style={{
color: isSelected ? color : "white",
fontSize: 14,
margin: 5,
}}
>
{typeName}
</Text>
</TouchableOpacity>
);
};

View File

@@ -1,11 +1,17 @@
type nameString =
| "Rapid"
| "LTDEXP"
| "NightLTDEXP"
| "SPCL"
| "Normal"
| string;
type colorString = "aqua" | "red" | "#297bff" | "#ff7300ff" | "#00869ecc" | "#727272cc" | "white" | "pink";
import { typeID } from "./getStringConfig";
export type colorString =
| "aqua"
| "red"
| "#297bff"
| "#ff7300ff"
| "#00869ecc"
| "#727272cc"
| "#00b8d8cc"
| "#e000b0ff"
| "white"
| "black"
| "pink";
type trainTypeString =
| "快速"
| "特急"
@@ -21,24 +27,35 @@ type trainTypeString =
| "単機回送"
| "その他";
type trainTypeDataString = "rapid" | "express" | "normal" | "notService";
type getTrainType = (d: nameString) => {
type getTrainType = (
d: typeID,
isWhiteMode?: boolean
) => {
color: colorString;
name: trainTypeString;
data: trainTypeDataString;
};
export const getTrainType: getTrainType = (nameString) => {
export const getTrainType: getTrainType = (nameString, whiteMode) => {
switch (nameString) {
case "Normal":
return { color: "white", name: "普通列車", data: "normal" };
return {
color: whiteMode ? "black" : "white",
name: "普通列車",
data: "normal",
};
case "OneMan":
return { color: "white", name: "普通列車(ワンマン)", data: "normal" };
return {
color: whiteMode ? "black" : "white",
name: "普通列車(ワンマン)",
data: "normal",
};
case "Rapid":
case "OneManRapid":
return { color: "aqua", name: "快速", data: "rapid" };
return { color: whiteMode ? "#00b8d8cc" : "aqua", name: "快速", data: "rapid" };
case "LTDEXP":
return { color: "red", name: "特急", data: "express" };
case "NightLTDEXP":
return { color: "pink", name: "寝台特急", data: "express" };
return { color: whiteMode ? "#e000b0ff":"pink", name: "寝台特急", data: "express" };
case "SPCL":
case "SPCL_Normal":
return { color: "#297bff", name: "臨時", data: "normal" };
@@ -55,6 +72,10 @@ export const getTrainType: getTrainType = (nameString) => {
case "FreightForwarding":
return { color: "#727272cc", name: "単機回送", data: "notService" };
default:
return { color: "white", name: "その他", data: "normal" };
return {
color: whiteMode ? "black" : "white",
name: "その他",
data: "normal",
};
}
};

View File

@@ -6,6 +6,7 @@ const initialState = {
allTrainDiagram: undefined,
setAllTrainDiagram: () => {},
allCustomTrainData: [],
keyList: [],
};
const AllTrainDiagramContext = createContext(initialState);