Merge commit 'b512efd3ec1f7d38a2d1a10cb159ad7f34f95818' into patch/6.x

This commit is contained in:
harukin-expo-dev-env 2025-07-08 16:43:42 +00:00
commit efc1631a78
57 changed files with 2800 additions and 1193 deletions

2
App.js
View File

@ -18,6 +18,7 @@ import { TrainMenuProvider } from "./stateBox/useTrainMenu";
import { buildProvidersTree } from "./lib/providerTreeProvider"; import { buildProvidersTree } from "./lib/providerTreeProvider";
import { StationListProvider } from "./stateBox/useStationList"; import { StationListProvider } from "./stateBox/useStationList";
import { NotificationProvider } from "./stateBox/useNotifications"; import { NotificationProvider } from "./stateBox/useNotifications";
import { UserPositionProvider } from "./stateBox/useUserPosition";
LogBox.ignoreLogs([ LogBox.ignoreLogs([
"ViewPropTypes will be removed", "ViewPropTypes will be removed",
@ -36,6 +37,7 @@ export default function App() {
const ProviderTree = buildProvidersTree([ const ProviderTree = buildProvidersTree([
AllTrainDiagramProvider, AllTrainDiagramProvider,
NotificationProvider, NotificationProvider,
UserPositionProvider,
StationListProvider, StationListProvider,
FavoriteStationProvider, FavoriteStationProvider,
TrainDelayDataProvider, TrainDelayDataProvider,

30
GeneralWebView.tsx Normal file
View File

@ -0,0 +1,30 @@
import React, { CSSProperties } from "react";
import { View, ViewProps } from "react-native";
import { WebView } from "react-native-webview";
import { BigButton } from "./components/atom/BigButton";
import { useNavigation } from "@react-navigation/native";
export default ({ route }) => {
if (!route.params) {
return null;
}
const { uri, useExitButton = true } = route.params;
const { goBack } = useNavigation();
return (
<View style={styles}>
<WebView
useWebKit
source={{ uri }}
onMessage={(event) => {
const { data } = event.nativeEvent;
const {type} = JSON.parse(data);
if (type === "windowClose") return goBack();
}}
/>
{useExitButton && <BigButton onPress={goBack} string="閉じる" />}
</View>
);
};
const styles: ViewProps["style"] = {
height: "100%",
backgroundColor: "#0099CC",
};

View File

@ -1,5 +1,10 @@
import React, { useEffect } from "react"; import React, { useEffect, useRef, useState } from "react";
import { createStackNavigator } from "@react-navigation/stack"; import { createStackNavigator } from "@react-navigation/stack";
import { useWindowDimensions, Platform } from "react-native";
import Constants from "expo-constants";
import { Dimensions, StatusBar } from "react-native";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { AS } from "./storageControl"; import { AS } from "./storageControl";
import TrainBase from "./components/trainbaseview"; import TrainBase from "./components/trainbaseview";
@ -10,14 +15,16 @@ import Setting from "./components/Settings/settings";
import { useFavoriteStation } from "./stateBox/useFavoriteStation"; import { useFavoriteStation } from "./stateBox/useFavoriteStation";
import { optionData } from "./lib/stackOption"; import { optionData } from "./lib/stackOption";
import AllTrainDiagramView from "./components/AllTrainDiagramView"; import AllTrainDiagramView from "./components/AllTrainDiagramView";
import { useCurrentTrain } from "./stateBox/useCurrentTrain";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { news } from "./config/newsUpdate"; import { news } from "./config/newsUpdate";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import GeneralWebView from "./GeneralWebView";
const Stack = createStackNavigator(); const Stack = createStackNavigator();
export function MenuPage() { export function MenuPage() {
const { favoriteStation, setFavoriteStation } = useFavoriteStation(); const { favoriteStation, setFavoriteStation } = useFavoriteStation();
const { getCurrentTrain } = useCurrentTrain(); const { height, width } = useWindowDimensions();
const tabBarHeight = useBottomTabBarHeight();
const navigation = useNavigation(); const navigation = useNavigation();
const { addListener } = navigation; const { addListener } = navigation;
useEffect(() => { useEffect(() => {
@ -42,8 +49,34 @@ export function MenuPage() {
}) })
.catch((error) => console.error("Error fetching icon setting:", error)); .catch((error) => console.error("Error fetching icon setting:", error));
}, []); }, []);
const scrollRef = useRef(null);
const [mapMode, setMapMode] = useState(false);
const [mapHeight, setMapHeight] = useState(0);
useEffect(() => {
const MapHeight =
height -
tabBarHeight +
(Platform.OS == "android" ? Constants.statusBarHeight : 0) -
100 -
((((width / 100) * 80) / 20) * 9 + 10 + 30);
setMapHeight(MapHeight);
}, [height, tabBarHeight, width]);
const [MapFullHeight, setMapFullHeight] = useState(0);
useEffect(() => {
const MapFullHeight =
height -
tabBarHeight +
(Platform.OS == "android" ? Constants.statusBarHeight : 0);
setMapFullHeight(MapFullHeight);
}, [height, tabBarHeight, width]);
useEffect(() => { useEffect(() => {
const unsubscribe = addListener("tabPress", (e) => { const unsubscribe = addListener("tabPress", (e) => {
scrollRef.current.scrollTo({
y: mapHeight - 80,
animated: true,
});
setMapMode(false);
AS.getItem("favoriteStation") AS.getItem("favoriteStation")
.then((d) => { .then((d) => {
const returnData = JSON.parse(d); const returnData = JSON.parse(d);
@ -55,7 +88,7 @@ export function MenuPage() {
}); });
return unsubscribe; return unsubscribe;
}, [navigation]); }, [navigation, mapHeight]);
return ( return (
<Stack.Navigator> <Stack.Navigator>
<Stack.Screen <Stack.Screen
@ -65,7 +98,15 @@ export function MenuPage() {
gestureEnabled: true, gestureEnabled: true,
headerTransparent: true, headerTransparent: true,
}} }}
children={() => <Menu getCurrentTrain={getCurrentTrain} />} children={() => (
<Menu
scrollRef={scrollRef}
mapHeight={mapHeight}
MapFullHeight={MapFullHeight}
mapMode={mapMode}
setMapMode={setMapMode}
/>
)}
/> />
<Stack.Screen name="news" options={optionData} component={News} /> <Stack.Screen name="news" options={optionData} component={News} />
<Stack.Screen <Stack.Screen
@ -88,6 +129,7 @@ export function MenuPage() {
component={AllTrainDiagramView} component={AllTrainDiagramView}
/> />
<Stack.Screen name="howto" options={optionData} component={HowTo} /> <Stack.Screen name="howto" options={optionData} component={HowTo} />
<Stack.Screen name="generalWebView" options={optionData} component={GeneralWebView} />
</Stack.Navigator> </Stack.Navigator>
); );
} }

2
Top.js
View File

@ -13,6 +13,7 @@ import { useTrainMenu } from "./stateBox/useTrainMenu";
import { AS } from "./storageControl"; import { AS } from "./storageControl";
import { news } from "./config/newsUpdate"; import { news } from "./config/newsUpdate";
import { Linking, Platform } from "react-native"; import { Linking, Platform } from "react-native";
import GeneralWebView from "./GeneralWebView";
const Stack = createStackNavigator(); const Stack = createStackNavigator();
export const Top = () => { export const Top = () => {
const { webview } = useCurrentTrain(); const { webview } = useCurrentTrain();
@ -64,6 +65,7 @@ export const Top = () => {
component={TrainBase} component={TrainBase}
/> />
<Stack.Screen name="howto" options={optionData} component={HowTo} /> <Stack.Screen name="howto" options={optionData} component={HowTo} />
<Stack.Screen name="generalWebView" options={optionData} component={GeneralWebView} />
<Stack.Screen name="news" options={optionData} component={News} /> <Stack.Screen name="news" options={optionData} component={News} />
<Stack.Screen <Stack.Screen
name="trainMenu" name="trainMenu"

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,6 +1,29 @@
{ {
"type": "FeatureCollection", "type": "FeatureCollection",
"features": [ "features": [
{
"properties": {
"name": "阿波池田",
"uri": "https://uedayou.net/jrslod/四国旅客鉄道/土讃線/阿波池田",
"color": "E25885"
},
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[133.80429, 34.02714],
[133.80515, 34.02656]
]
}
},
{
"properties": {
"name": "阿波池田",
"uri": "https://uedayou.net/jrslod/四国旅客鉄道/土讃線/阿波池田"
},
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [133.80429, 34.02714] }
},
{ {
"properties": { "properties": {
"name": "佃", "name": "佃",
@ -561,6 +584,29 @@
"type": "Feature", "type": "Feature",
"geometry": { "type": "Point", "coordinates": [134.53819, 34.08082] } "geometry": { "type": "Point", "coordinates": [134.53819, 34.08082] }
}, },
{
"properties": {
"name": "徳島",
"uri": "https://uedayou.net/jrslod/四国旅客鉄道/高徳線/徳島",
"color": "9ACD32"
},
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[134.55251, 34.07404],
[134.55049, 34.07498]
]
}
},
{
"properties": {
"name": "徳島",
"uri": "https://uedayou.net/jrslod/四国旅客鉄道/高徳線/徳島"
},
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [134.55251, 34.07404] }
},
{ {
"properties": { "properties": {
"name": "徳島線", "name": "徳島線",

View File

@ -33,7 +33,7 @@ export const LandscapeTrainInfo = (props) => {
stickyHeaderIndices={[1]} stickyHeaderIndices={[1]}
scrollEventThrottle={16} scrollEventThrottle={16}
onScroll={(d) => { onScroll={(d) => {
console.log(d.nativeEvent.contentOffset.y); // Handle scroll events
}} }}
> >
<View style={{ height: 0 }} /> <View style={{ height: 0 }} />

View File

@ -44,7 +44,8 @@ export const EachTrainInfoCore = ({
}) => { }) => {
const { currentTrain } = useCurrentTrain(); const { currentTrain } = useCurrentTrain();
const { originalStationList, stationList } = useStationList(); const { originalStationList, stationList } = useStationList();
const { allTrainDiagram: trainList } = useAllTrainDiagram(); const { allTrainDiagram: trainList, allCustonTrainData } =
useAllTrainDiagram();
const { setTrainInfo } = useTrainMenu(); const { setTrainInfo } = useTrainMenu();
const [currentTrainData, setCurrentTrainData] = useState(); const [currentTrainData, setCurrentTrainData] = useState();
@ -170,12 +171,12 @@ export const EachTrainInfoCore = ({
const position = points.findIndex((d) => d == true); const position = points.findIndex((d) => d == true);
let isThrew = false; let isThrew = false;
if (position == -1) return () => {}; if (position == -1) return () => {};
setShowThrew(true);
if (trainDataWidhThrough[position].split(",")[1] == "通過") { if (trainDataWidhThrough[position].split(",")[1] == "通過") {
LayoutAnimation.configureNext({ LayoutAnimation.configureNext({
duration: 400, duration: 400,
update: { type: "easeInEaseOut", springDamping: 0.6 }, update: { type: "easeInEaseOut", springDamping: 0.6 },
}); });
setShowThrew(true);
isThrew = true; isThrew = true;
} }
if (position < 5) { if (position < 5) {
@ -322,7 +323,7 @@ export const EachTrainInfoCore = ({
}, []); }, []);
const openTrainInfo = (d) => { const openTrainInfo = (d) => {
const train = customTrainDataDetector(d); const train = customTrainDataDetector(d, allCustonTrainData);
let TrainNumber = ""; let TrainNumber = "";
if (train.trainNumDistance != undefined) { if (train.trainNumDistance != undefined) {
const timeInfo = const timeInfo =

View File

@ -1,14 +1,15 @@
import React, { CSSProperties, FC, useEffect, useMemo, useState } from "react"; import React, { CSSProperties, FC, useEffect, useMemo, useState } from "react";
import { Text, View, LayoutAnimation, TextStyle } from "react-native"; import { Text, View, TextStyle, TouchableOpacity } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { getType } from "../../../lib/eachTrainInfoCoreLib/getType";
import { migrateTrainName } from "../../../lib/eachTrainInfoCoreLib/migrateTrainName"; import { migrateTrainName } from "../../../lib/eachTrainInfoCoreLib/migrateTrainName";
import { TrainIconStatus } from "./trainIconStatus"; import { TrainIconStatus } from "./trainIconStatus";
import { TrainViewIcon } from "./trainViewIcon"; import { TrainViewIcon } from "./trainViewIcon";
import { OneManText } from "./HeaderTextParts/OneManText"; import { OneManText } from "./HeaderTextParts/OneManText";
import { customTrainDataDetector } from "@/components/custom-train-data"; import { customTrainDataDetector } from "@/components/custom-train-data";
import { InfogramText } from "@/components/ActionSheetComponents/EachTrainInfoCore/HeaderTextParts/InfogramText"; import { InfogramText } from "@/components/ActionSheetComponents/EachTrainInfoCore/HeaderTextParts/InfogramText";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { useNotification } from "@/stateBox/useNotifications";
type Props = { type Props = {
data: { trainNum: string; limited: string }; data: { trainNum: string; limited: string };
@ -38,14 +39,21 @@ export const HeaderText: FC<Props> = ({
tailStation, tailStation,
navigate, navigate,
from, from,
scrollHandlers scrollHandlers,
}) => { }) => {
const { limited, trainNum } = data; const { limited, trainNum } = data;
const { updatePermission } = useTrainMenu();
const { allCustonTrainData } = useAllTrainDiagram();
const { expoPushToken } = useNotification();
// 列車名、種別、フォントの取得 // 列車名、種別、フォントの取得
const [typeName, trainName, fontAvailable, isOneMan, infogram] = const [typeName, trainName, fontAvailable, isOneMan, infogram] =
useMemo(() => { useMemo(() => {
const customTrainData = customTrainDataDetector(trainNum); const customTrainData = customTrainDataDetector(
trainNum,
allCustonTrainData
);
const [type, fontAvailable, isOneMan] = (() => { const [type, fontAvailable, isOneMan] = (() => {
switch (customTrainData.type) { switch (customTrainData.type) {
case "LTDEXP": case "LTDEXP":
@ -118,10 +126,27 @@ export const HeaderText: FC<Props> = ({
}, [trainData]); }, [trainData]);
return ( return (
<View style={{ padding: 10, flexDirection: "row", alignItems: "center" }} onTouchStart={()=>scrollHandlers.ref.current?.scrollTo({ y: 0, animated: true })}>
<TrainIconStatus {...{ data, navigate, from }} />
<View <View
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
onTouchStart={() =>
scrollHandlers.ref.current?.scrollTo({ y: 0, animated: true })
}
>
<TrainIconStatus {...{ data, navigate, from }} />
<TouchableOpacity
style={{ borderRadius: 5, flexDirection: "row", alignItems: "center" }} style={{ borderRadius: 5, flexDirection: "row", alignItems: "center" }}
onLongPress={() => {
navigate("generalWebView", {
uri:
"https://jr-shikoku-data-post-system.pages.dev?trainNum=" +
trainNum +
"&token=" +
expoPushToken,
useExitButton: false,
});
SheetManager.hide("EachTrainInfo");
}}
disabled={!updatePermission}
> >
<Text <Text
style={{ style={{
@ -137,7 +162,7 @@ export const HeaderText: FC<Props> = ({
{isOneMan && <OneManText />} {isOneMan && <OneManText />}
<Text style={textConfig}>{trainName}</Text> <Text style={textConfig}>{trainName}</Text>
<InfogramText infogram={infogram} /> <InfogramText infogram={infogram} />
</View> </TouchableOpacity>
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
<Text style={textConfig}> <Text style={textConfig}>

View File

@ -6,6 +6,7 @@ import { useInterval } from "../../../lib/useInterval";
import { Icon } from "@expo/vector-icons/build/createIconSet"; import { Icon } from "@expo/vector-icons/build/createIconSet";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { customTrainDataDetector } from "../../custom-train-data"; import { customTrainDataDetector } from "../../custom-train-data";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
type GlyphNames = ComponentProps<typeof Ionicons>["name"]; type GlyphNames = ComponentProps<typeof Ionicons>["name"];
@ -22,11 +23,14 @@ export const TrainIconStatus: FC<Props> = ({ data, navigate, from }) => {
const [trainIcon, setTrainIcon] = useState(null); const [trainIcon, setTrainIcon] = useState(null);
const [anpanmanStatus, setAnpanmanStatus] = useState<apt>(); const [anpanmanStatus, setAnpanmanStatus] = useState<apt>();
const [address, setAddress] = useState(""); const [address, setAddress] = useState("");
const { allCustonTrainData } = useAllTrainDiagram();
useEffect(() => { useEffect(() => {
if (!data.trainNum) return; if (!data.trainNum) return;
const { trainIcon, infoUrl } = customTrainDataDetector(data.trainNum); const { img, infoUrl } = customTrainDataDetector(
if (trainIcon) setTrainIcon(trainIcon); data.trainNum,
allCustonTrainData
);
if (img) setTrainIcon(img);
if (infoUrl) setAddress(infoUrl); if (infoUrl) setAddress(infoUrl);
switch (data.trainNum) { switch (data.trainNum) {
@ -47,7 +51,6 @@ export const TrainIconStatus: FC<Props> = ({ data, navigate, from }) => {
) )
.then((d) => d.json()) .then((d) => d.json())
.then((d) => { .then((d) => {
console.log(d);
if (d.trainStatus == "") { if (d.trainStatus == "") {
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"}); //setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
} else if (d.trainStatus == "▲") { } else if (d.trainStatus == "▲") {
@ -72,7 +75,6 @@ export const TrainIconStatus: FC<Props> = ({ data, navigate, from }) => {
) )
.then((d) => d.json()) .then((d) => d.json())
.then((d) => { .then((d) => {
console.log(d);
if (d.trainStatus == "") { if (d.trainStatus == "") {
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"}); //setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
} else if (d.trainStatus == "▲") { } else if (d.trainStatus == "▲") {
@ -104,6 +106,7 @@ export const TrainIconStatus: FC<Props> = ({ data, navigate, from }) => {
}); });
SheetManager.hide("EachTrainInfo"); SheetManager.hide("EachTrainInfo");
}} }}
disabled={!address}
> >
{move ? ( {move ? (
<Image <Image

View File

@ -0,0 +1,146 @@
import React, { useRef } from "react";
import { View, Platform, Text } from "react-native";
import ActionSheet ,{ ScrollView } from "react-native-actions-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ListItem } from "@rneui/themed";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { Linking } from "react-native";
import TouchableScale from "react-native-touchable-scale";
export const Social = () => {
const actionSheetRef = useRef(null);
const insets = useSafeAreaInsets();
return (
<ActionSheet
gestureEnabled
CustomHeaderComponent={<></>}
ref={actionSheetRef}
isModal={Platform.OS == "ios"}
containerStyle={
Platform.OS == "android"
? {
paddingBottom: insets.bottom,
}
: {}
}
useBottomSafeAreaPadding={Platform.OS == "android"}
>
<View
style={{
backgroundColor: "#0099CC",
borderTopRadius: 5,
borderColor: "dark",
borderWidth: 1,
height: "100%",
}}
>
<View style={{ height: 26, width: "100%", backgroundColor: "#0099CC" }}>
<View
style={{
height: 6,
width: 45,
borderRadius: 100,
backgroundColor: "#f0f0f0",
marginVertical: 10,
alignSelf: "center",
}}
/>
</View>
<View
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
>
<MaterialCommunityIcons
name="web"
style={{ padding: 5 }}
color="white"
size={30}
/>
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
JR四国公式SNS一族
</Text>
</View>
<ScrollView
style={{
padding: 10,
backgroundColor: "white",
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
flex:1
}}
>
{[
{
url: "https://twitter.com/jr_shikoku_info",
name: "JR四国列車運行情報",
},
{
url: "https://twitter.com/JRshikoku_eigyo",
name: "JR四国営業部【公式】",
},
{
url: "https://twitter.com/JRshikoku_tokyo",
name: "JR四国 東京営業情報【公式】",
},
{
url: "https://twitter.com/JRshikoku_osaka",
name: "JR四国 大阪営業部【公式】",
},
{
url: "https://twitter.com/jrs_matsuyama",
name: "JR四国 松山駅 【公式】",
},
{
url: "https://twitter.com/jrshikoku_kochi",
name: "JR四国 高知駅【公式】",
},
{
url: "https://twitter.com/jr_tokust",
name: "JR四国 徳島駅【公式】",
},
{
url: "https://twitter.com/jrshikoku_uwjm",
name: "JR四国 宇和島駅【公式】",
},
{
url: "https://twitter.com/jrshikoku_wkama",
name: "JR四国 ワープ高松支店【公式】",
},
{
url: "https://twitter.com/JRshikoku_wkoch",
name: "JR四国 ワープ高知支店【公式】",
},
{
url: "https://twitter.com/Yoakemonogatari",
name: "志国土佐 時代の夜明けのものがたり【公式】",
},
{
url: "https://twitter.com/Smile_Eki_Chan",
name: "すまいるえきちゃん♡JR四国【公式】",
},
{
url: "https://twitter.com/sper_ponchan",
name: "しこくたぬきのぽんちゃん 【四国家サポーターズクラブ】",
},
].map((d) => (
<ListItem
bottomDivider
onPress={() => Linking.openURL(d.url)}
key={d.url}
friction={90} //
tension={100} // These props are passed to the parent component (here TouchableScale)
activeScale={0.95} //
Component={TouchableScale}
>
<ListItem.Content>
<ListItem.Title>{d.name}</ListItem.Title>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
))}
</ScrollView>
</View>
</ActionSheet>
);
};

View File

@ -0,0 +1,51 @@
import React, { useRef } from "react";
import { View, Platform } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { SpecialTrainInfoBox } from "../Menu/SpecialTrainInfoBox";
export const SpecialTrainInfo = ({ payload }) => {
const { navigate } = payload;
const actionSheetRef = useRef(null);
const insets = useSafeAreaInsets();
return (
<ActionSheet
gestureEnabled
CustomHeaderComponent={<></>}
ref={actionSheetRef}
isModal={Platform.OS == "ios"}
containerStyle={
Platform.OS == "android"
? {
paddingBottom: insets.bottom,
}
: {}
}
useBottomSafeAreaPadding={Platform.OS == "android"}
>
<View
style={{
backgroundColor: "#0099CC",
borderTopRadius: 5,
borderColor: "dark",
borderWidth: 1,
}}
>
<View style={{ height: 26, width: "100%", backgroundColor: "#0099CC" }}>
<View
style={{
height: 6,
width: 45,
borderRadius: 100,
backgroundColor: "#f0f0f0",
marginVertical: 10,
alignSelf: "center",
}}
/>
</View>
<SpecialTrainInfoBox navigate={navigate} />
</View>
</ActionSheet>
);
};

View File

@ -2,19 +2,14 @@ import React, { useState, useEffect } from "react";
import { import {
View, View,
Linking, Linking,
Text,
TouchableOpacity,
BackHandler, BackHandler,
Platform, Platform,
useWindowDimensions,
} from "react-native"; } from "react-native";
import AutoHeightImage from "react-native-auto-height-image";
import { FontAwesome, Foundation, Ionicons } from "@expo/vector-icons";
import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useSafeAreaInsets } from "react-native-safe-area-context";
import ActionSheet, { SheetManager } from "react-native-actions-sheet"; import ActionSheet, { SheetManager } from "react-native-actions-sheet";
import Sign from "../../components/駅名表/Sign"; import Sign from "../../components/駅名表/Sign";
import { TicketBox } from "../atom/TicketBox";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
import { getPDFViewURL } from "../../lib/getPdfViewURL"; import { getPDFViewURL } from "../../lib/getPdfViewURL";
import { useBusAndTrainData } from "../../stateBox/useBusAndTrainData"; import { useBusAndTrainData } from "../../stateBox/useBusAndTrainData";
import { AS } from "../../storageControl"; import { AS } from "../../storageControl";
@ -23,17 +18,19 @@ import { TrainBusButton } from "./StationDeteilView/TrainBusButton";
import { 駅構内図 } from "./StationDeteilView/StationInsideMapButton"; import { 駅構内図 } from "./StationDeteilView/StationInsideMapButton";
import { WebSiteButton } from "./StationDeteilView/WebSiteButton"; import { WebSiteButton } from "./StationDeteilView/WebSiteButton";
import { StationTimeTableButton } from "./StationDeteilView/StationTimeTableButton"; import { StationTimeTableButton } from "./StationDeteilView/StationTimeTableButton";
import { StationTrainPositionButton } from "./StationDeteilView/StationTrainPositionButton";
export const StationDeteilView = (props) => { export const StationDeteilView = (props) => {
if (!props.payload) return <></>; if (!props.payload) return <></>;
const { currentStation, navigate, onExit, goTo, useShow } = props.payload; const { currentStation, navigate, onExit, goTo, useShow } = props.payload;
const { width } = useWindowDimensions();
const { busAndTrainData } = useBusAndTrainData(); const { busAndTrainData } = useBusAndTrainData();
const [trainBus, setTrainBus] = useState(); const [trainBus, setTrainBus] = useState();
useEffect(() => { useEffect(() => {
if (!currentStation) return () => {}; if (!currentStation) return () => {};
const data = busAndTrainData.filter((d) => const data = busAndTrainData.filter(
d.name === currentStation[0].Station_JP (d) => d.name === currentStation[0].Station_JP
); );
if (data.length == 0) { if (data.length == 0) {
setTrainBus(); setTrainBus();
@ -89,9 +86,10 @@ export const StationDeteilView = (props) => {
</View> </View>
<View> <View>
{currentStation && ( {currentStation && (
<View style={{ margin: 10, marginHorizontal: wp("10%") }}> <>
<View style={{ margin: 10, marginHorizontal: width * 0.1 }}>
<Sign <Sign
currentStation={currentStation} stationID={currentStation[0].StationNumber}
oP={() => { oP={() => {
usePDFView == "true" usePDFView == "true"
? Linking.openURL(currentStation[0].StationTimeTable) ? Linking.openURL(currentStation[0].StationTimeTable)
@ -102,12 +100,18 @@ export const StationDeteilView = (props) => {
}); });
onExit(); onExit();
}} }}
oLP={() => Linking.openURL(currentStation[0].StationTimeTable)} oLP={() =>
Linking.openURL(currentStation[0].StationTimeTable)
}
/> />
</View> </View>
)} <View style={{ flexDirection: "row" }}>
{currentStation && <StationTrainPositionButton
currentStation[0].JrHpUrl && stationNumber={currentStation[0].StationNumber}
onExit={onExit}
navigate={navigate}
/>
{currentStation[0].JrHpUrl &&
currentStation[0].StationNumber != "M12" && ( currentStation[0].StationNumber != "M12" && (
<駅構内図 //児島例外/ <駅構内図 //児島例外/
navigate={navigate} navigate={navigate}
@ -117,7 +121,7 @@ export const StationDeteilView = (props) => {
onExit={onExit} onExit={onExit}
/> />
)} )}
{currentStation && ( </View>
<View style={{ flexDirection: "row" }}> <View style={{ flexDirection: "row" }}>
{!currentStation[0].JrHpUrl || ( {!currentStation[0].JrHpUrl || (
<WebSiteButton <WebSiteButton
@ -139,9 +143,12 @@ export const StationDeteilView = (props) => {
useShow={useShow} useShow={useShow}
/> />
)} )}
{!currentStation[0].StationMap || ( <StationMapButton
<StationMapButton stationMap={currentStation[0].StationMap} /> stationMap={
)} currentStation[0].StationMap ||
`https://www.google.co.jp/maps/place/${currentStation[0].lat},${currentStation[0].lng}`
}
/>
{!trainBus || ( {!trainBus || (
<TrainBusButton <TrainBusButton
address={trainBus.address} address={trainBus.address}
@ -156,6 +163,7 @@ export const StationDeteilView = (props) => {
/> />
)} )}
</View> </View>
</>
)} )}
</View> </View>
</View> </View>

View File

@ -19,6 +19,7 @@ export const 駅構内図:FC<Props> = (props) => {
alignContent: "center", alignContent: "center",
alignItems: "center", alignItems: "center",
margin: 2, margin: 2,
flex: 1,
}} }}
onPress={() => { onPress={() => {
navigate("howto", { info, goTo, useShow }); navigate("howto", { info, goTo, useShow });

View File

@ -0,0 +1,54 @@
import { FC } from "react";
import { TouchableOpacity, View, Text, Linking } from "react-native";
import { useStationList } from "@/stateBox/useStationList";
import { useCurrentTrain } from "@/stateBox/useCurrentTrain";
import AntDesign from "react-native-vector-icons/AntDesign";
type Props = {
stationNumber: string;
onExit: () => void;
navigate?: (screen: string, params: { screen: string }) => void;
};
export const StationTrainPositionButton: FC<Props> = (props) => {
const { stationNumber, onExit, navigate } = props;
const {
inject,
} = useCurrentTrain();
const { getInjectJavascriptAddress } = useStationList();
return (
<TouchableOpacity
style={{
height: 50,
backgroundColor: "#0099CC",
flexDirection: "row",
alignContent: "center",
alignItems: "center",
margin: 2,
flex: 1
}}
onPress={() => {
navigate("positions", { screen: "Apps" });
const script = getInjectJavascriptAddress(stationNumber);
inject(script);
onExit();
}}
>
<View style={{ flex: 1 }} />
<AntDesign
name={"barchart"}
size={20}
color={"white"}
style={{ marginHorizontal: 5, marginVertical: 5 }}
/>
<Text
style={{
color: "white",
textAlign: "center",
textAlignVertical: "center",
}}
>
</Text>
<View style={{ flex: 1 }} />
</TouchableOpacity>
);
};

View File

@ -4,11 +4,15 @@ import { JRSTraInfo } from "./JRSTraInfo";
import { StationDeteilView } from "./StationDeteilView"; import { StationDeteilView } from "./StationDeteilView";
import { TrainMenuLineSelector } from "./TrainMenuLineSelector"; import { TrainMenuLineSelector } from "./TrainMenuLineSelector";
import { TrainIconUpdate } from "./TrainIconUpdate"; import { TrainIconUpdate } from "./TrainIconUpdate";
import { SpecialTrainInfo } from "./SpecialTrainInfo";
import { Social } from "./SocialMenu";
registerSheet("EachTrainInfo", EachTrainInfo); registerSheet("EachTrainInfo", EachTrainInfo);
registerSheet("JRSTraInfo", JRSTraInfo); registerSheet("JRSTraInfo", JRSTraInfo);
registerSheet("StationDetailView", StationDeteilView); registerSheet("StationDetailView", StationDeteilView);
registerSheet("TrainMenuLineSelector", TrainMenuLineSelector); registerSheet("TrainMenuLineSelector", TrainMenuLineSelector);
registerSheet("TrainIconUpdate", TrainIconUpdate); registerSheet("TrainIconUpdate", TrainIconUpdate);
registerSheet("SpecialTrainInfo", SpecialTrainInfo);
registerSheet("Social", Social);
export {}; export {};

View File

@ -21,7 +21,7 @@ import { BigButton } from "./atom/BigButton";
import { Switch } from "react-native-elements"; import { Switch } from "react-native-elements";
export default function AllTrainDiagramView() { export default function AllTrainDiagramView() {
const { goBack, navigate } = useNavigation(); const { goBack, navigate } = useNavigation();
const { keyList, allTrainDiagram } = useAllTrainDiagram(); const { keyList, allTrainDiagram, allCustonTrainData } = useAllTrainDiagram();
const [input, setInput] = useState(""); // 文字入力 const [input, setInput] = useState(""); // 文字入力
const [keyBoardVisible, setKeyBoardVisible] = useState(false); const [keyBoardVisible, setKeyBoardVisible] = useState(false);
const [useStationName, setUseStationName] = useState(false); const [useStationName, setUseStationName] = useState(false);
@ -53,7 +53,7 @@ export default function AllTrainDiagramView() {
}, []); }, []);
const openTrainInfo = (d) => { const openTrainInfo = (d) => {
const train = customTrainDataDetector(d); const train = customTrainDataDetector(d, allCustonTrainData);
let TrainNumber = ""; let TrainNumber = "";
if (train.trainNumDistance != undefined) { if (train.trainNumDistance != undefined) {
const timeInfo = const timeInfo =
@ -74,6 +74,7 @@ export default function AllTrainDiagramView() {
return ( return (
<View style={{ backgroundColor: "#0099CC", height: "100%" }}> <View style={{ backgroundColor: "#0099CC", height: "100%" }}>
<FlatList <FlatList
contentContainerStyle={{ justifyContent: "flex-end", flexGrow: 1 }}
style={{ flex: 1 }} style={{ flex: 1 }}
data={keyList?.filter((d) => { data={keyList?.filter((d) => {
if (useStationName) { if (useStationName) {
@ -93,6 +94,14 @@ export default function AllTrainDiagramView() {
return d.includes(input); return d.includes(input);
})} })}
renderItem={({ item }) => <Item {...{ openTrainInfo, id: item }} />} renderItem={({ item }) => <Item {...{ openTrainInfo, id: item }} />}
ListEmptyComponent={
<View style={{ flex: 1, alignItems: "center", marginTop: 50 }}>
<Text style={{ color: "white", fontSize: 20 }}>
検索結果がありません
</Text>
</View>
}
keyExtractor={(item) => item} keyExtractor={(item) => item}
//initialNumToRender={100} //initialNumToRender={100}
/> />

View File

@ -2,7 +2,13 @@ import React from "react";
import { Platform, LayoutAnimation } from "react-native"; import { Platform, LayoutAnimation } from "react-native";
import { WebView } from "react-native-webview"; import { WebView } from "react-native-webview";
import { lineList, stationNamePair } from "../../lib/getStationList"; import {
lineList,
lineList_LineWebID,
lineListPair,
stationIDPair,
stationNamePair,
} from "../../lib/getStationList";
import { checkDuplicateTrainData } from "../../lib/checkDuplicateTrainData"; import { checkDuplicateTrainData } from "../../lib/checkDuplicateTrainData";
import { useFavoriteStation } from "../../stateBox/useFavoriteStation"; import { useFavoriteStation } from "../../stateBox/useFavoriteStation";
import { useCurrentTrain } from "../../stateBox/useCurrentTrain"; import { useCurrentTrain } from "../../stateBox/useCurrentTrain";
@ -17,7 +23,7 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
const { navigate } = useNavigation(); const { navigate } = useNavigation();
const { favoriteStation } = useFavoriteStation(); const { favoriteStation } = useFavoriteStation();
const { isLandscape } = useDeviceOrientationChange(); const { isLandscape } = useDeviceOrientationChange();
const { originalStationList, stationList } = useStationList(); const { originalStationList, stationList, getInjectJavascriptAddress } = useStationList();
const { const {
setSelectedLine, setSelectedLine,
mapsStationData: stationData, mapsStationData: stationData,
@ -129,20 +135,11 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
const onLoadEnd = () => { const onLoadEnd = () => {
if (once) return () => {}; if (once) return () => {};
if (!stationData) return () => {}; if (!stationData) return () => {};
if (!originalStationList) return () => {};
if (favoriteStation.length < 1) return () => {}; if (favoriteStation.length < 1) return () => {};
const getStationLine = (now) => { const string = getInjectJavascriptAddress(favoriteStation[0][0].StationNumber);
const returnData = Object.keys(stationData).filter((d) => { if (!string) return () => {};
const cache = stationData[d].findIndex( webview?.current.injectJavaScript(string);
(data) => data.Station_JP == now.Station_JP
);
return cache != -1;
});
return returnData[0];
};
const lineName = getStationLine(favoriteStation[0][0]);
webview.current?.injectJavaScript(
`MoveDisplayStation('${lineName}_${favoriteStation[0][0].MyStation}_${favoriteStation[0][0].Station_JP}')`
);
once = true; once = true;
}; };

View File

@ -8,11 +8,13 @@ import { useNavigation } from "@react-navigation/native";
import { useTrainMenu } from "../stateBox/useTrainMenu"; import { useTrainMenu } from "../stateBox/useTrainMenu";
import { FavoriteListItem } from "./atom/FavoriteListItem"; import { FavoriteListItem } from "./atom/FavoriteListItem";
import { BigButton } from "./atom/BigButton"; import { BigButton } from "./atom/BigButton";
import { useStationList } from "@/stateBox/useStationList";
export const FavoriteList: FC = () => { export const FavoriteList: FC = () => {
const { favoriteStation } = useFavoriteStation(); const { favoriteStation } = useFavoriteStation();
const { webview } = useCurrentTrain(); const { webview } = useCurrentTrain();
const { navigate, addListener, goBack, canGoBack } = useNavigation(); const { navigate, addListener, goBack, canGoBack } = useNavigation();
const { mapsStationData: stationData } = useTrainMenu(); const { mapsStationData: stationData } = useTrainMenu();
const { getInjectJavascriptAddress } = useStationList();
useEffect(() => { useEffect(() => {
const unsubscribe = addListener("tabPress", goToTrainMenu); const unsubscribe = addListener("tabPress", goToTrainMenu);
@ -38,27 +40,16 @@ export const FavoriteList: FC = () => {
</Text> </Text>
<ScrollView style={{ height: "100%", backgroundColor: "white" }}> <ScrollView style={{ height: "100%", backgroundColor: "white" }}>
{favoriteStation {favoriteStation
.filter((d) => d[0].StationMap)
.map((currentStation) => { .map((currentStation) => {
return ( return (
<FavoriteListItem <FavoriteListItem
currentStation={currentStation} currentStation={currentStation}
onPress={() => { onPress={() => {
const getStationLine = (now) => { const scriptString = getInjectJavascriptAddress(
const returnData = Object.keys(stationData).filter((d) => { currentStation[0].StationNumber
const cache = stationData[d].findIndex(
(data) => data.Station_JP == now.Station_JP
);
return cache != -1;
});
return returnData[0];
};
const lineName = getStationLine(currentStation[0]);
webview.current?.injectJavaScript(
`MoveDisplayStation('${lineName}_${currentStation[0].MyStation}_${currentStation[0].Station_JP}');
document.getElementById("disp").insertAdjacentHTML("afterbegin", "<div />");`
); );
if (!scriptString) return;
webview.current?.injectJavaScript(scriptString);
goBack(); goBack();
if (canGoBack()) goBack(); if (canGoBack()) goBack();
}} }}

View File

@ -0,0 +1,186 @@
import Sign from "@/components/駅名表/Sign";
import React, { useEffect, useRef, useState } from "react";
import { AS } from "@/storageControl";
import {
useWindowDimensions,
View,
LayoutAnimation,
TouchableOpacity,
Text,
ScrollView,
} from "react-native";
import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";
import { SheetManager } from "react-native-actions-sheet";
import { StationNumber } from "../StationPagination";
import { SimpleDot } from "../SimpleDot";
export const CarouselBox = ({
originalStationList,
listUpStation,
nearPositionStation,
setListIndex,
listIndex,
navigate,
stationListMode,
isSearchMode
}) => {
const carouselRef = useRef<ICarouselInstance>(null);
const { height, width } = useWindowDimensions();
const [dotButton, setDotButton] = useState(false);
const carouselBadgeScrollViewRef = useRef<ScrollView>(null);
useEffect(() => {
if (!carouselBadgeScrollViewRef.current) return;
const dotSize = dotButton ? 28 : 24;
const scrollToIndex = dotSize * listIndex - width / 2 + dotSize - 5;
carouselBadgeScrollViewRef.current.scrollTo({
x: scrollToIndex,
animated: true,
});
}, [listIndex, dotButton, width, carouselBadgeScrollViewRef]);
const oPSign = () => {
const payload = {
currentStation: listUpStation[listIndex],
navigate,
goTo: "menu",
//@ts-ignore
useShow: () => SheetManager.show("StationDetailView", { payload }),
onExit: () => SheetManager.hide("StationDetailView"),
};
//@ts-ignore
SheetManager.show("StationDetailView", { payload });
};
const oLPSign = () => {
LayoutAnimation.configureNext({
duration: 600,
update: { type: "spring", springDamping: 0.5 },
});
AS.setItem(
"CarouselSettings/activeDotSettings",
!dotButton ? "true" : "false"
);
setDotButton(!dotButton);
};
useEffect(() => {
AS.getItem("CarouselSettings/activeDotSettings").then((data) => {
setDotButton(data === "true");
});
}, []);
const RenderItem = ({ item, index }) => {
return (
<View
style={{
backgroundColor: "#0000",
width,
flexDirection: "row",
marginLeft: 0,
marginRight: 0,
}}
key={item[0].StationNumber}
>
<View style={{ flex: 1 }} />
{item[0].StationNumber != "null" ? (
<Sign
stationID={item[0].StationNumber}
isCurrentStation={item == nearPositionStation}
oP={oPSign}
oLP={oLPSign}
/>
) : (
<TouchableOpacity
style={{
width: width * 0.8,
height: ((width * 0.8) / 20) * 9,
borderColor: "#0099CC",
borderWidth: 1,
backgroundColor: "white",
}}
>
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={{ color: "#0099CC", fontSize: 20 }}>
{!!isSearchMode ? "路線検索モードです。上に並んでいる路線を選んでみましょう!" :stationListMode == "position"
? "現在地の近くに駅がありません。"
: "お気に入りリストがありません。お気に入りの駅を追加しよう!"}
</Text>
</View>
</TouchableOpacity>
)}
<View style={{ flex: 1 }} />
</View>
);
};
return (
<View style={{ flex: 1, paddingTop: 10 }}>
<Carousel
ref={carouselRef}
data={
listUpStation.length > 0
? listUpStation
: [[{ StationNumber: "null" }]]
}
height={(((width / 100) * 80) / 20) * 9 + 10}
pagingEnabled={true}
snapEnabled={true}
loop={false}
width={width}
style={{ width: width, alignContent: "center" }}
mode="parallax"
modeConfig={{
parallaxScrollingScale: 1,
parallaxScrollingOffset: 100,
parallaxAdjacentItemScale: 0.8,
}}
scrollAnimationDuration={600}
onSnapToItem={setListIndex}
renderItem={RenderItem}
overscrollEnabled={false}
defaultIndex={listIndex >= listUpStation.length ? 0 : listIndex}
/>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
flexDirection: "row",
justifyContent: "center",
alignContent: "center",
alignItems: "center",
paddingVertical: 2,
paddingHorizontal: 10,
minWidth: width,
}}
ref={(scrollViewRef) => {
// ScrollViewのrefを保存
if (scrollViewRef) {
carouselBadgeScrollViewRef.current = scrollViewRef;
}
}}
>
{originalStationList &&
listUpStation.map((d, index) => {
const active = index == listIndex;
const numberKey = d[0].StationNumber + index;
return dotButton ? (
<StationNumber
onPress={() => setListIndex(index)}
currentStation={d}
active={active}
key={numberKey}
/>
) : (
<SimpleDot
onPress={() => setListIndex(index)}
active={active}
key={numberKey}
/>
);
})}
</ScrollView>
</View>
);
};

View File

@ -0,0 +1,174 @@
import { AS } from "@/storageControl";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import React, { useEffect, useRef } from "react";
import { useWindowDimensions } from "react-native";
import {
View,
TouchableOpacity,
Text,
LayoutAnimation,
KeyboardAvoidingView,
Platform,
} from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { SearchUnitBox } from "@/components/Menu/RailScope/SearchUnitBox";
export const CarouselTypeChanger = ({
locationStatus,
position,
mapsRef,
scrollRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation,
mapMode,
setMapMode,
isSearchMode,
setisSearchMode,
}) => {
const tabBarHeight = useBottomTabBarHeight();
const { height, width } = useWindowDimensions();
const returnToDefaultMode = () => {
LayoutAnimation.configureNext({
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
});
setMapMode(false);
};
return (
<KeyboardAvoidingView
behavior="padding"
keyboardVerticalOffset={80}
enabled={Platform.OS === "ios"}
style={{
width: "100%",
height: 40,
flexDirection: "row",
position: mapMode ? "absolute" : "relative",
bottom: mapMode ? 0 : undefined,
zIndex: 1000,
backgroundColor: "white",
}}
key={"carouselTypeChanger"}
>
<SearchUnitBox
isSearchMode={isSearchMode}
setisSearchMode={setisSearchMode}
/>
<TouchableOpacity
style={{
flex: 1,
backgroundColor:
stationListMode == "position" ? "#0099CC" : "#0099CC80",
padding: 5,
alignItems: "center",
flexDirection: "row",
marginHorizontal: 5,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
borderBottomLeftRadius: mapMode ? 0 : 20,
borderBottomRightRadius: mapMode ? 0 : 20,
}}
disabled={!locationStatus}
onPressIn={() => {
if (!position) return;
returnToDefaultMode();
setStationListMode("position");
AS.setItem("stationListMode", "position");
setSelectedCurrentStation(0);
}}
onPress={() => {
if (!position) return;
returnToDefaultMode();
setStationListMode("position");
AS.setItem("stationListMode", "position");
setSelectedCurrentStation(0);
}}
>
<Ionicons
name="locate-outline"
size={14}
color="white"
style={{ margin: 5 }}
/>
<Text
style={{
color: "white",
fontSize: 14,
fontWeight: "bold",
flex: 1,
textAlign: "center",
}}
>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
padding: 5,
alignItems: "center",
flexDirection: "row",
marginHorizontal: 5,
borderRadius: 50,
}}
onPressIn={() => returnToDefaultMode()}
>
<Ionicons
name={!mapMode ? "menu" : "chevron-up-outline"}
size={30}
color="#0099CC"
style={{ marginHorizontal: 5 }}
/>
</TouchableOpacity>
<TouchableOpacity
style={{
flex: 1,
backgroundColor:
stationListMode == "favorite" ? "#0099CC" : "#0099CC80",
padding: 5,
alignItems: "center",
flexDirection: "row",
marginHorizontal: 5,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
borderBottomLeftRadius: mapMode ? 0 : 20,
borderBottomRightRadius: mapMode ? 0 : 20,
}}
onPressIn={() => {
returnToDefaultMode();
// お気に入りリスト更新
setStationListMode("favorite");
AS.setItem("stationListMode", "favorite");
setSelectedCurrentStation(0);
}}
onPress={() => {
returnToDefaultMode();
// お気に入りリスト更新
setStationListMode("favorite");
AS.setItem("stationListMode", "favorite");
setSelectedCurrentStation(0);
}}
>
<Ionicons name="star" size={14} color="white" style={{ margin: 5 }} />
<Text
style={{
color: "white",
fontSize: 14,
fontWeight: "bold",
flex: 1,
textAlign: "center",
}}
>
</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
};

View File

@ -6,10 +6,12 @@ import {
MaterialCommunityIcons, MaterialCommunityIcons,
} from "@expo/vector-icons"; } from "@expo/vector-icons";
import { ListItem } from "@rneui/themed"; import { ListItem } from "@rneui/themed";
import TouchableScale from 'react-native-touchable-scale'; import TouchableScale from "react-native-touchable-scale";
import Icon from "react-native-vector-icons/Entypo"; import Icon from "react-native-vector-icons/Entypo";
import { TextBox } from "../atom/TextBox"; import { TextBox } from "../atom/TextBox";
import { TicketBox } from "../atom/TicketBox"; import { TicketBox } from "../atom/TicketBox";
import { SpecialTrainInfoBox } from "./SpecialTrainInfoBox";
import { SheetManager } from "react-native-actions-sheet";
export const FixedContentBottom = (props) => { export const FixedContentBottom = (props) => {
return ( return (
@ -54,7 +56,9 @@ export const FixedContentBottom = (props) => {
backgroundColor="red" backgroundColor="red"
flex={1} flex={1}
onPressButton={() => onPressButton={() =>
Linking.openURL("https://xprocess.haruk.in/JR-shikoku-Apps-Common/2025-update-status") Linking.openURL(
"https://xprocess.haruk.in/JR-shikoku-Apps-Common/2025-update-status"
)
} }
> >
<Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}> <Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}>
@ -82,7 +86,9 @@ export const FixedContentBottom = (props) => {
backgroundColor="#0099CC" backgroundColor="#0099CC"
flex={1} flex={1}
onPressButton={() => onPressButton={() =>
Linking.openURL("https://www.jr-shikoku.co.jp/sp/index.html#menu-box") SheetManager.show("SpecialTrainInfo", {
payload: { navigate: props.navigate },
})
} }
> >
<Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}> <Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}>
@ -179,105 +185,20 @@ export const FixedContentBottom = (props) => {
<Text style={{ color: "white" }}>(通話料がかかります)</Text> <Text style={{ color: "white" }}>(通話料がかかります)</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View <TextBox
style={{ backgroundColor="#0099CC"
backgroundColor: "#0099CC", flex={1}
borderRadius: 10, onPressButton={() =>
margin: 10, SheetManager.show("Social")
borderColor: "black", }
borderWidth: 2,
}}
> >
<View <Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}>
style={{ padding: 10, flexDirection: "row", alignItems: "center" }} ソーシャルメディア
>
<MaterialCommunityIcons
name="twitter"
style={{ padding: 5 }}
color="white"
size={30}
/>
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
JR四国公式Twitter一族
</Text> </Text>
</View> <Text style={{ color: "white", fontSize: 18 }}>
<View JR四国のSNS一覧です
style={{ </Text>
padding: 10, </TextBox>
backgroundColor: "white",
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
}}
>
{[
{
url: "https://twitter.com/jr_shikoku_info",
name: "JR四国列車運行情報",
},
{
url: "https://twitter.com/JRshikoku_eigyo",
name: "JR四国営業部【公式】",
},
{
url: "https://twitter.com/JRshikoku_tokyo",
name: "JR四国 東京営業情報【公式】",
},
{
url: "https://twitter.com/JRshikoku_osaka",
name: "JR四国 大阪営業部【公式】",
},
{
url: "https://twitter.com/jrs_matsuyama",
name: "JR四国 松山駅 【公式】",
},
{
url: "https://twitter.com/jrshikoku_kochi",
name: "JR四国 高知駅【公式】",
},
{
url: "https://twitter.com/jr_tokust",
name: "JR四国 徳島駅【公式】",
},
{
url: "https://twitter.com/jrshikoku_uwjm",
name: "JR四国 宇和島駅【公式】",
},
{
url: "https://twitter.com/jrshikoku_wkama",
name: "JR四国 ワープ高松支店【公式】",
},
{
url: "https://twitter.com/JRshikoku_wkoch",
name: "JR四国 ワープ高知支店【公式】",
},
{
url: "https://twitter.com/Yoakemonogatari",
name: "志国土佐 時代の夜明けのものがたり【公式】",
},
{
url: "https://twitter.com/Smile_Eki_Chan",
name: "すまいるえきちゃん♡JR四国【公式】",
},
{
url: "https://twitter.com/sper_ponchan",
name: "しこくたぬきのぽんちゃん 【四国家サポーターズクラブ】",
},
].map((d) => (
<ListItem bottomDivider onPress={() => Linking.openURL(d.url)}
key={d.url}friction={90} //
tension={100} // These props are passed to the parent component (here TouchableScale)
activeScale={0.95} //
Component={TouchableScale}
>
<ListItem.Content>
<ListItem.Title>{d.name}</ListItem.Title>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
))}
</View>
</View>
<Text style={{ fontWeight: "bold", fontSize: 20 }}>上級者向け機能</Text> <Text style={{ fontWeight: "bold", fontSize: 20 }}>上級者向け機能</Text>
<TextBox <TextBox
backgroundColor="#8c00d6" backgroundColor="#8c00d6"

View File

@ -0,0 +1,111 @@
import React from "react";
import {
View,
Text,
TouchableOpacity,
ScrollView,
StyleProp,
ViewStyle,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { SheetManager } from "react-native-actions-sheet";
import LottieView from "lottie-react-native";
import { useTrainDelayData } from "@/stateBox/useTrainDelayData";
import dayjs from "dayjs";
export const JRSTraInfoBox = () => {
const { getTime, delayData, loadingDelayData, setLoadingDelayData } =
useTrainDelayData();
const styles: { [key: string]: StyleProp<ViewStyle> } = {
touch: {
backgroundColor: "#0099CC",
borderRadius: 5,
margin: 10,
borderColor: "black",
borderWidth: 2,
overflow: "hidden",
},
scroll: {
backgroundColor: "#0099CC",
borderRadius: 5,
maxHeight: 300,
},
bottom: {
position: "absolute",
top: 250,
alignItems: "center",
width: "100%",
height: 50,
backgroundColor: "#007FCC88",
},
box: {
padding: 10,
backgroundColor: "white",
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
},
};
return (
<TouchableOpacity
onPress={() => SheetManager.show("JRSTraInfo")}
style={styles.touch}
>
<ScrollView scrollEnabled={false} style={styles.scroll}>
<View
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
>
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
EX
</Text>
<View style={{ flex: 1 }} />
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
{getTime ? dayjs(getTime).format("HH:mm") : NaN}
</Text>
<Ionicons
name="reload"
color="white"
size={30}
style={{ margin: 5 }}
onPress={() => setLoadingDelayData(true)}
/>
</View>
<View style={styles.box}>
{loadingDelayData ? (
<View style={{ alignItems: "center" }}>
<LottieView
autoPlay
loop
style={{ width: 150, height: 150, backgroundColor: "#fff" }}
source={require("@/assets/51690-loading-diamonds.json")}
/>
</View>
) : delayData ? (
delayData.map((d, index, array) => {
let data = d.split(" ");
return (
<View
style={{ flexDirection: "row" }}
key={data[1] + "key" + index}
>
<Text style={{ flex: 15, fontSize: 18 }}>
{data[0].replace("\n", "")}
</Text>
<Text style={{ flex: 5, fontSize: 18 }}>{data[1]}</Text>
<Text style={{ flex: 6, fontSize: 18 }}>{data[3]}</Text>
</View>
);
})
) : (
<Text>5</Text>
)}
</View>
</ScrollView>
<View style={styles.bottom}>
<View style={{ flex: 1 }} />
<Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}>
</Text>
<View style={{ flex: 1 }} />
</View>
</TouchableOpacity>
);
};

View File

@ -0,0 +1,114 @@
import React from "react";
import {
TouchableOpacity,
Text,
View,
LayoutAnimation,
TextInput,
KeyboardAvoidingView,
Platform,
} from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { useWindowDimensions } from "react-native";
import lineColorList from "@/assets/originData/lineColorList";
import { lineList_LineWebID, stationIDPair } from "@/lib/getStationList";
export const SearchUnitBox = ({ isSearchMode, setisSearchMode }) => {
const { height, width } = useWindowDimensions();
return (
<>
<TouchableOpacity
style={{
position: "absolute",
bottom: !!isSearchMode ? 0 : 60,
right: 0,
padding: !!isSearchMode ? 5 : 10,
margin: !!isSearchMode ? 0 : 10,
backgroundColor: "#0099CC",
borderRadius: !!isSearchMode ? 5 : 50,
width: !!isSearchMode ? width : 50,
zIndex: 1000,
}}
disabled={!!isSearchMode}
onPress={() => {
LayoutAnimation.configureNext({
duration: 100,
update: { type: "easeInEaseOut", springDamping: 0.6 },
});
setisSearchMode(true);
}}
>
{!isSearchMode && <Ionicons name="search" size={30} color="white" />}
{!!isSearchMode && (
<View style={{ backgroundColor: "#0099CC" }}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TouchableOpacity onPress={() => {
LayoutAnimation.configureNext({
duration: 100,
update: { type: "easeInEaseOut", springDamping: 0.6 },
});
setisSearchMode(false);
}}>
<Ionicons
name="arrow-back"
size={20}
color="white"
style={{ marginRight: 10 }}
/>
</TouchableOpacity>
{Object.keys(lineList_LineWebID).map((d) => (
<TouchableOpacity
style={{
flex: 1,
backgroundColor: lineColorList[stationIDPair[lineList_LineWebID[d]]],
padding: 5,
marginHorizontal: 2,
borderRadius: 10,
borderColor: "white",
borderWidth: 1,
borderStyle: "solid",
alignItems: "center",
opacity: isSearchMode == stationIDPair[lineList_LineWebID[d]] ? 1 : !isSearchMode ? 1 : 0.5,
zIndex: 10,
}}
onPress={() => {
const id = stationIDPair[lineList_LineWebID[d]];
const s = isSearchMode == id ? undefined : id;
if (!s) return;
setisSearchMode(s);
}}
key={stationIDPair[lineList_LineWebID[d]]}
>
<Text
style={{ color: "white", fontWeight: "bold", fontSize: 20 }}
>
{stationIDPair[lineList_LineWebID[d]]}
</Text>
</TouchableOpacity>
))}
{/* <View
style={{
backgroundColor: "white",
borderRadius: 25,
height: 30,
paddingRight: 10,
paddingLeft: 10,
flex: 1,
}}
>
<TextInput
placeholder="駅名を入力してフィルタリングします。"
//onFocus={() => setKeyBoardVisible(true)}
onEndEditing={() => {}}
//onChange={(ret) => setInput(ret.nativeEvent.text)}
//value={input}
style={{ flex: 1 }}
/>
</View> */}
</View>
</View>
)}
</TouchableOpacity>
</>
);
};

View File

@ -0,0 +1,66 @@
import { FC, useEffect, useLayoutEffect, useState } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { getPDFViewURL } from "@/lib/getPdfViewURL";
import { ScrollView, SheetManager } from "react-native-actions-sheet";
type props = {
navigate: (screen: string, params?: object) => void;
};
export const SpecialTrainInfoBox: FC<props> = ({ navigate }) => {
const [specialData, setSpecialData] = useState([]);
useLayoutEffect(() => {
fetch("https://n8n.haruk.in/webhook/sptrainfo")
.then((res) => res.json())
.then((data) => setSpecialData(data.data))
.catch((err) => console.log(err));
}, []);
return (
<View
style={{
backgroundColor: "#0099CC",
}}
>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text
style={{
fontSize: 30,
fontWeight: "bold",
color: "white",
paddingHorizontal: 10,
paddingVertical: 5,
}}
>
</Text>
</View>
<ScrollView
style={{
backgroundColor: "white",
}}
>
{specialData.map((d) => (
<TouchableOpacity
onPress={() => {
navigate("howto", {
info: getPDFViewURL("https://www.jr-shikoku.co.jp" + d.address),
goTo: "menu",
});
SheetManager.hide("SpecialTrainInfo");
}}
onLongPress={() => alert(d.description)}
key={d.address}
style={{
padding: 10,
borderBottomWidth: 1,
borderBottomColor: "#ccc",
flexDirection: "row",
alignItems: "center",
}}
>
<Text style={{ color: "black", fontSize: 20 }}>{d.text}</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
};

View File

@ -1,18 +1,25 @@
import { View, TouchableOpacity, Linking } from "react-native"; import { View, TouchableOpacity, Linking,Platform, Image, useWindowDimensions } from "react-native";
import AutoHeightImage from "react-native-auto-height-image"; import Constants from "expo-constants";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
export const TitleBar = () => { export const TitleBar = () => {
const { width } = useWindowDimensions();
return ( return (
<View style={{ alignItems: "center" }}> <View
style={{
alignItems: "center",
position: "absolute",
top: 0,
left: 0,
right: 0,
zIndex: 100,
paddingTop: Platform.OS == "ios" ? Constants.statusBarHeight : 0,
}}
>
<TouchableOpacity <TouchableOpacity
onPress={() => Linking.openURL("https://www.jr-shikoku.co.jp")} onPress={() => Linking.openURL("https://www.jr-shikoku.co.jp")}
> >
<AutoHeightImage <Image source={require("../../assets/Header.png")} style={{ width: width, resizeMode: "contain", backgroundColor: "white", height: 80 }} />
source={require("../../assets/Header.png")}
resizeMode="contain"
width={wp("100%")}
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
); );

View File

@ -0,0 +1,52 @@
import React from "react";
import { Linking, View } from "react-native";
import { UsefulBox } from "@/components/TrainMenu/UsefulBox";
import MaterialCommunityIcons from "@expo/vector-icons/build/MaterialCommunityIcons";
export const TopMenuButton = () => {
const buttonList:{
backgroundColor: string;
icon: keyof typeof MaterialCommunityIcons.glyphMap;
onPress: () => void;
title: string;
}[] = [
{
backgroundColor: "#F89038",
icon: "train-car",
onPress: () =>
Linking.openURL("https://www.jr-shikoku.co.jp/01_trainbus/sp/"),
title: "駅・鉄道情報",
},
{
backgroundColor: "#EA4752",
icon: "google-spreadsheet",
onPress: () =>
Linking.openURL(
"https://www.jr-shikoku.co.jp/01_trainbus/jikoku/sp/#mainprice-box"
),
title: "運賃表",
},
{
backgroundColor: "#91C31F",
icon: "clipboard-list-outline",
onPress: () => Linking.openURL("https://www.jr-shikoku.co.jp/e5489/"),
title: "予約",
},
];
return (
<View style={{ flexDirection: "row" }}>
{buttonList.map((d, index) => (
<UsefulBox
backgroundColor={d.backgroundColor}
icon={d.icon}
flex={1}
onPressButton={d.onPress}
key={index + d.icon}
>
{d.title}
</UsefulBox>
))}
</View>
);
};

View File

@ -2,44 +2,18 @@ import React, { useEffect, useState } from "react";
import Icon from "react-native-vector-icons/Entypo"; import Icon from "react-native-vector-icons/Entypo";
import { View, Text, TouchableOpacity, LayoutAnimation } from "react-native"; import { View, Text, TouchableOpacity, LayoutAnimation } from "react-native";
import lineColorList from "../../../assets/originData/lineColorList"; import lineColorList from "../../../assets/originData/lineColorList";
import Ionicons from "react-native-vector-icons/Ionicons";
import { AS } from "../../../storageControl"; import { AS } from "../../../storageControl";
export const FavoriteSettingsItem = ({ export const FavoriteSettingsItem = ({ currentStation }) => {
currentStation,
setFavoriteStation,
index,
array,
}) => {
const lineIDs = []; const lineIDs = [];
const EachIDs = []; const EachIDs = [];
console.log(currentStation);
currentStation.forEach((d) => { currentStation.forEach((d) => {
if (!d.StationNumber) return; if (!d.StationNumber) return;
const textArray = d.StationNumber.split(""); const textArray = d.StationNumber.split("");
lineIDs.push(textArray.filter((s) => "A" < s && s < "Z").join("")); lineIDs.push(textArray.filter((s) => "A" < s && s < "Z").join(""));
EachIDs.push(textArray.filter((s) => "0" <= s && s <= "9").join("")); EachIDs.push(textArray.filter((s) => "0" <= s && s <= "9").join(""));
}); });
const [head, setHead] = useState(false);
const [tail, setTail] = useState(false);
useEffect(() => {
switch (true) {
case array.length == 1:
setHead(true);
setTail(true);
break;
case index == 0:
setHead(true);
setTail(false);
break;
case index == array.length - 1:
setHead(false);
setTail(true);
break;
default:
setHead(false);
setTail(false);
}
}, [array]);
return ( return (
<View style={{ flexDirection: "row", backgroundColor: "white" }}> <View style={{ flexDirection: "row", backgroundColor: "white" }}>
@ -47,7 +21,7 @@ export const FavoriteSettingsItem = ({
style={{ style={{
width: 35, width: 35,
position: "relative", position: "relative",
marginHorizontal: 15, marginHorizontal: 10,
flexDirection: "row", flexDirection: "row",
height: "101%", height: "101%",
}} }}
@ -91,43 +65,15 @@ export const FavoriteSettingsItem = ({
> >
<Text style={{ fontSize: 20 }}>{currentStation[0].Station_JP}</Text> <Text style={{ fontSize: 20 }}>{currentStation[0].Station_JP}</Text>
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
<TouchableOpacity </View>
style={{ marginHorizontal: 10, marginVertical: 4, width: 30 }} <View
onPress={() => { style={{
console.log("up"); alignContent: "center",
LayoutAnimation.configureNext( alignItems: "center",
LayoutAnimation.Presets.easeInEaseOut alignSelf: "center",
);
const removedStation = [...array].filter((d, i) => {
if (i == index) return false;
return true;
});
removedStation.splice(index - 1, 0, currentStation);
setFavoriteStation(removedStation);
AS.setItem("favoriteStation", JSON.stringify(removedStation));
}} }}
> >
{head ? null : <Icon name="chevron-up" size={26} />} <Ionicons name={"reorder-two"} size={20} style={{ marginHorizontal: 10 }} />
</TouchableOpacity>
<TouchableOpacity
style={{ marginHorizontal: 10, marginVertical: 4, width: 30 }}
onPress={() => {
console.log("down");
LayoutAnimation.configureNext(
LayoutAnimation.Presets.easeInEaseOut
);
const removedStation = [...array].filter((d, i) => {
if (i == index) return false;
return true;
});
removedStation.splice(index + 1, 0, currentStation);
setFavoriteStation(removedStation);
AS.setItem("favoriteStation", JSON.stringify(removedStation));
}}
>
{tail ? null : <Icon name="chevron-down" size={26} />}
</TouchableOpacity>
</View> </View>
</View> </View>
); );

View File

@ -1,31 +1,58 @@
import React from "react"; import React, { useCallback } from "react";
import { View, Text, TouchableOpacity, ScrollView } from "react-native"; import { View, Text, StyleSheet } from "react-native";
import Animated, { useAnimatedRef } from "react-native-reanimated";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import Sortable from "react-native-sortables";
import { useFavoriteStation } from "../../stateBox/useFavoriteStation"; import { useFavoriteStation } from "../../stateBox/useFavoriteStation";
import { CheckBox } from "react-native-elements";
import { FavoriteSettingsItem } from "./FavoliteSettings/FavoiliteSettingsItem"; import { FavoriteSettingsItem } from "./FavoliteSettings/FavoiliteSettingsItem";
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem"; import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
import { AS } from "@/storageControl";
export const FavoriteSettings = () => { export const FavoriteSettings = () => {
const { favoriteStation, setFavoriteStation } = useFavoriteStation(); const { favoriteStation, setFavoriteStation } = useFavoriteStation();
const scrollableRef = useAnimatedRef();
const { goBack } = useNavigation(); const { goBack } = useNavigation();
const renderItem = useCallback((props) => {
const { item, index } = props;
return (
<FavoriteSettingsItem currentStation={item} key={item[0].StationNumber} />
);
}, []);
return ( return (
<View style={{ height: "100%", backgroundColor: "#0099CC" }}> <View style={{ height: "100%", backgroundColor: "#0099CC" }}>
<SheetHeaderItem <SheetHeaderItem
title="お気に入り設定" title="お気に入り設定"
LeftItem={{ title: " 設定", onPress: goBack }} LeftItem={{ title: " 設定", onPress: goBack }}
/> />
<ScrollView style={{ flex: 1, backgroundColor: "white" }}> <Animated.ScrollView
{favoriteStation.map((currentStation, index, array) => ( style={{ flex: 1, backgroundColor: "white" }}
<FavoriteSettingsItem contentContainerStyle={styles.contentContainer}
currentStation={currentStation} ref={scrollableRef}
setFavoriteStation={setFavoriteStation} >
index={index} <Sortable.Grid
array={array} columnGap={0}
key={currentStation[0].StationNumber} columns={1}
data={favoriteStation}
renderItem={renderItem}
rowGap={0}
scrollableRef={scrollableRef} // required for auto scroll
snapOffsetY={0}
onDragEnd={(newOrder) => {
const newFavoriteStation = newOrder.indexToKey.map(
(item, index, array) => {
let returnData = [];
favoriteStation.forEach((station) => {
if (station[0].StationNumber === item) returnData = station;
});
return returnData;
}
);
setFavoriteStation(newFavoriteStation);
AS.setItem("favoriteStation", JSON.stringify(newFavoriteStation));
}}
keyExtractor={(item) => item[0].StationNumber}
/> />
))} </Animated.ScrollView>
</ScrollView>
<Text <Text
style={{ style={{
backgroundColor: "white", backgroundColor: "white",
@ -39,20 +66,19 @@ export const FavoriteSettings = () => {
); );
}; };
const SimpleSwitch = ({ bool, setBool, str }) => ( const styles = StyleSheet.create({
<View style={{ flexDirection: "row" }}> card: {
<CheckBox alignItems: "center",
checked={bool == "true" ? true : false} backgroundColor: "#36877F",
checkedColor="red" borderRadius: 10,
onPress={() => setBool(bool == "true" ? "false" : "true")} height: 100,
containerStyle={{ justifyContent: "center",
flex: 1, },
backgroundColor: "#00000000", contentContainer: {
borderColor: "white", padding: 10,
alignContent: "center", },
}} text: {
textStyle={{ fontSize: 20, fontWeight: "normal" }} color: "white",
title={str} fontWeight: "bold",
/> },
</View> });
);

View File

@ -18,6 +18,8 @@ export const LayoutSettings = ({
setUsePDFView, setUsePDFView,
trainMenu, trainMenu,
setTrainMenu, setTrainMenu,
uiSetting,
setUiSetting,
trainPosition, trainPosition,
setTrainPosition, setTrainPosition,
headerSize, headerSize,
@ -38,6 +40,17 @@ export const LayoutSettings = ({
falseText={"本家\n(文字アイコン)"} falseText={"本家\n(文字アイコン)"}
trueText={"オリジナル\n(車種アイコン)"} trueText={"オリジナル\n(車種アイコン)"}
/> />
<SwitchArea
str="列車表示"
bool={uiSetting}
setBool={setUiSetting}
falseImage={require("../../assets/configuration/layout_default.jpg")}
trueImage={require("../../assets/configuration/layout_tokyo.jpg")}
falseText={"本家"}
trueText={"オリジナル"}
falseValue="default"
trueValue="tokyo"
/>
<SwitchArea <SwitchArea
str="トップメニュー表示" str="トップメニュー表示"
bool={mapSwitch} bool={mapSwitch}

View File

@ -17,7 +17,7 @@ import { SwitchArea } from "../atom/SwitchArea";
import { useNotification } from "../../stateBox/useNotifications"; import { useNotification } from "../../stateBox/useNotifications";
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem"; import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
const versionCode = "6.0.2"; const versionCode = "6.1.0"; // Update this version code as needed
export const SettingTopPage = ({ export const SettingTopPage = ({
testNFC, testNFC,

View File

@ -39,6 +39,7 @@ export default function Setting(props) {
const [trainPosition, setTrainPosition] = useState(false); const [trainPosition, setTrainPosition] = useState(false);
const [headerSize, setHeaderSize] = useState("default"); const [headerSize, setHeaderSize] = useState("default");
const [startPage, setStartPage] = useState(false); const [startPage, setStartPage] = useState(false);
const [uiSetting, setUiSetting] = useState("tokyo");
useLayoutEffect(() => { useLayoutEffect(() => {
AS.getItem("iconSwitch").then(setIconSetting); AS.getItem("iconSwitch").then(setIconSetting);
AS.getItem("mapSwitch").then(setMapSwitch); AS.getItem("mapSwitch").then(setMapSwitch);
@ -48,6 +49,7 @@ export default function Setting(props) {
AS.getItem("trainPositionSwitch").then(setTrainPosition); AS.getItem("trainPositionSwitch").then(setTrainPosition);
AS.getItem("headerSize").then(setHeaderSize); AS.getItem("headerSize").then(setHeaderSize);
AS.getItem("startPage").then(setStartPage); AS.getItem("startPage").then(setStartPage);
AS.getItem("uiSetting").then(setUiSetting);
}, []); }, []);
const testNFC = async () => { const testNFC = async () => {
//const result = await ExpoFelicaReader.scan(); //const result = await ExpoFelicaReader.scan();
@ -63,6 +65,7 @@ export default function Setting(props) {
AS.setItem("trainPositionSwitch", trainPosition.toString()), AS.setItem("trainPositionSwitch", trainPosition.toString()),
AS.setItem("headerSize", headerSize), AS.setItem("headerSize", headerSize),
AS.setItem("startPage", startPage.toString()), AS.setItem("startPage", startPage.toString()),
AS.setItem("uiSetting", uiSetting),
]).then(() => Updates.reloadAsync()); ]).then(() => Updates.reloadAsync());
}; };
return ( return (
@ -114,6 +117,8 @@ export default function Setting(props) {
setTrainMenu={setTrainMenu} setTrainMenu={setTrainMenu}
trainPosition={trainPosition} trainPosition={trainPosition}
setTrainPosition={setTrainPosition} setTrainPosition={setTrainPosition}
uiSetting={uiSetting}
setUiSetting={setUiSetting}
testNFC={testNFC} testNFC={testNFC}
updateAndReload={updateAndReload} updateAndReload={updateAndReload}
headerSize={headerSize} headerSize={headerSize}

View File

@ -1,6 +1,7 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Marker } from "react-native-maps"; import { Marker } from "react-native-maps";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useStationList } from "@/stateBox/useStationList";
type Props = { type Props = {
index: number; index: number;
indexBase: number; indexBase: number;
@ -13,7 +14,8 @@ type Props = {
export const MapPin: FC<Props> = (props) => { export const MapPin: FC<Props> = (props) => {
const { index, indexBase, latlng, D, d, navigate, webview } = props; const { index, indexBase, latlng, D, d, navigate, webview } = props;
const {goBack} = useNavigation(); const { goBack } = useNavigation();
const { getInjectJavascriptAddress } = useStationList();
return ( return (
<Marker <Marker
key={index + indexBase} key={index + indexBase}
@ -22,10 +24,9 @@ export const MapPin: FC<Props> = (props) => {
longitude: parseFloat(latlng[1]), longitude: parseFloat(latlng[1]),
}} }}
onPress={() => { onPress={() => {
webview.current?.injectJavaScript( const address = getInjectJavascriptAddress(D.StationNumber);
`MoveDisplayStation('${d}_${D.MyStation}_${D.Station_JP}'); if (!address) return;
document.getElementById("disp").insertAdjacentHTML("afterbegin", "<div />");` webview.current?.injectJavaScript(address);
);
if (navigate) goBack(); if (navigate) goBack();
}} }}
image={require("../../assets/reccha-small.png")} image={require("../../assets/reccha-small.png")}

View File

@ -8,6 +8,8 @@ export const SwitchArea = ({
trueImage, trueImage,
falseText, falseText,
trueText, trueText,
falseValue = false,
trueValue = true,
children, children,
}) => { }) => {
return ( return (
@ -37,7 +39,7 @@ export const SwitchArea = ({
bool={bool} bool={bool}
setBool={setBool} setBool={setBool}
color="red" color="red"
value={false} value={falseValue}
image={falseImage} image={falseImage}
subText={falseText} subText={falseText}
/> />
@ -45,7 +47,7 @@ export const SwitchArea = ({
bool={bool} bool={bool}
setBool={setBool} setBool={setBool}
color="red" color="red"
value={true} value={trueValue}
image={trueImage} image={trueImage}
subText={trueText} subText={trueText}
/> />

View File

@ -1,22 +0,0 @@
import { TouchableOpacity, Text } from "react-native";
import { MaterialCommunityIcons } from "@expo/vector-icons";
export const UsefulBox = (props) => {
const { icon, backgroundColor, flex, onPressButton, children } = props;
return (
<TouchableOpacity
style={{
flex: flex,
backgroundColor: backgroundColor,
padding: 10,
alignItems: "center",
margin: 2,
}}
onPress={onPressButton}
>
<MaterialCommunityIcons name={icon} color="white" size={50} />
<Text style={{ color: "white", fontWeight: "bold", fontSize: 18 }}>
{children}
</Text>
</TouchableOpacity>
);
};

View File

@ -1,5 +1,19 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
export const customTrainDataDetector = (TrainNumber: string) => { export const customTrainDataDetector = (
TrainNumber: string,
allCustonTrainData?: any[]
) => {
if (allCustonTrainData && allCustonTrainData.length > 0) {
const customTrain = allCustonTrainData.find(
(train) => train.TrainNumber === TrainNumber
);
if (customTrain) {
return customTrain;
}
}
const trainGetText = `?trainNum=${TrainNumber}&month=${dayjs().format(
"M"
)}&day=${dayjs().format("D")}`;
switch (TrainNumber) { switch (TrainNumber) {
//しおかぜメイン //しおかぜメイン
//8000 ノーマル //8000 ノーマル
@ -23,23 +37,23 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "しおかぜ", trainName: "しおかぜ",
trainIcon: "https://storage.haruk.in/s8000nr.png", img: "https://storage.haruk.in/s8000nr.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html",
trainNumDistance: 0, trainNumDistance: 0,
info: "いしづちと併結 / 8000系で運転", info: "いしづちと併結 / 8000系で運転",
infogram: "" infogram: "",
}; };
case "2M": case "2M":
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "しおかぜ", trainName: "しおかぜ",
trainIcon: "https://storage.haruk.in/s8000nr.png", img: "https://storage.haruk.in/s8000nr.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html",
trainNumDistance: 0, trainNumDistance: 0,
info: "8000系で運転", info: "8000系で運転",
infogram: "" infogram: "",
}; };
//8000 アンパン //8000 アンパン
case "10M": case "10M":
@ -49,11 +63,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "しおかぜ", trainName: "しおかぜ",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html", infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html",
trainNumDistance: 0, trainNumDistance: 0,
info: "いしづちと併結 / アンパンマン列車で運転", info: "いしづちと併結 / アンパンマン列車で運転",
infogram: "" infogram: "",
}; };
//8600 //8600
case "8M": case "8M":
@ -67,12 +81,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "しおかぜ", trainName: "しおかぜ",
trainIcon: "https://storage.haruk.in/s8600.png", img: "https://storage.haruk.in/s8600.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shiokaze.html",
trainNumDistance: 0, trainNumDistance: 0,
info: "いしづちと併結 / 8600系で運転", info: "いしづちと併結 / 8600系で運転",
infogram: "" infogram: "",
}; };
//いしづちメイン //いしづちメイン
@ -98,12 +112,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: "https://storage.haruk.in/s8000no.png", img: "https://storage.haruk.in/s8000no.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html",
trainNumDistance: 1000, trainNumDistance: 1000,
info: "しおかぜと併結 / 8000系で運転", info: "しおかぜと併結 / 8000系で運転",
infogram: "" infogram: "",
}; };
//8000 アンパン //8000 アンパン
@ -114,11 +128,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html", infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html",
trainNumDistance: 1000, trainNumDistance: 1000,
info: "しおかぜと併結 / アンパンマン列車で運転", info: "しおかぜと併結 / アンパンマン列車で運転",
infogram: "" infogram: "",
}; };
//8600 //8600
@ -133,12 +147,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: "https://storage.haruk.in/s8600_isz.png", img: "https://storage.haruk.in/s8600_isz.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html",
trainNumDistance: 1000, trainNumDistance: 1000,
info: "しおかぜと併結 / 8600系で運転", info: "しおかぜと併結 / 8600系で運転",
infogram: "" infogram: "",
}; };
//MEXP //MEXP
@ -147,24 +161,24 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "モーニングEXP高松", trainName: "モーニングEXP高松",
trainIcon: "https://storage.haruk.in/s8000nr.png", img: "https://storage.haruk.in/s8000nr.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/morning.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/morning.html",
trainNumDistance: null, trainNumDistance: null,
info: "8000系で運転", info: "8000系で運転",
infogram: "" infogram: "",
}; };
//8600 //8600
case "1091M": case "1091M":
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "モーニングEXP松山", trainName: "モーニングEXP松山",
trainIcon: "https://storage.haruk.in/s8600_isz.png", img: "https://storage.haruk.in/s8600_isz.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/morning.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/morning.html",
trainNumDistance: null, trainNumDistance: null,
info: "8600系で運転", info: "8600系で運転",
infogram: "" infogram: "",
}; };
//三桁いしづち //三桁いしづち
//8000 アンパン //8000 アンパン
@ -173,11 +187,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html", infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html",
trainNumDistance: 940, trainNumDistance: 940,
info: "アンパンマン列車で運転", info: "アンパンマン列車で運転",
infogram: "" infogram: "",
}; };
//8600 //8600
case "1043M": case "1043M":
@ -185,23 +199,23 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: "https://storage.haruk.in/s8600_isz.png", img: "https://storage.haruk.in/s8600_isz.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html",
trainNumDistance: 940, trainNumDistance: 940,
info: "8600系で運転", info: "8600系で運転",
infogram: "" infogram: "",
}; };
case "1046M": case "1046M":
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "いしづち", trainName: "いしづち",
trainIcon: "https://storage.haruk.in/s8600_isz.png", img: "https://storage.haruk.in/s8600_isz.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ishizuchi.html",
trainNumDistance: 940, trainNumDistance: 940,
info: "8600系で運転", info: "8600系で運転",
infogram: "" infogram: "",
}; };
//南風 2700ーマル //南風 2700ーマル
@ -226,11 +240,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "南風", trainName: "南風",
trainIcon: "https://storage.haruk.in/s2700.png", img: "https://storage.haruk.in/s2700.png",
infoUrl: "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/nanpu.html", infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/nanpu.html",
trainNumDistance: 30, trainNumDistance: 30,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
//2700アンパン //2700アンパン
@ -247,11 +262,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "南風", trainName: "南風",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: "https://www.jr-eki.com/aptrain/naani/dosan/train.html", infoUrl: "https://www.jr-eki.com/aptrain/naani/dosan/train.html",
trainNumDistance: 30, trainNumDistance: 30,
info: "アンパンマン列車で運転", info: "アンパンマン列車で運転",
infogram: "" infogram: "",
}; };
//うずしお //うずしお
@ -272,12 +287,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "うずしお", trainName: "うずしお",
trainIcon: "https://storage.haruk.in/s2700_uzu.png", img: "https://storage.haruk.in/s2700_uzu.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html",
trainNumDistance: 3000, trainNumDistance: 3000,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
//2700 二両編成 //2700 二両編成
@ -294,12 +309,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "うずしお", trainName: "うずしお",
trainIcon: "https://storage.haruk.in/s2700_uzu.png", img: "https://storage.haruk.in/s2700_uzu.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html",
trainNumDistance: 3000, trainNumDistance: 3000,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
//2600 //2600
@ -316,15 +331,14 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "うずしお", trainName: "うずしお",
trainIcon: "https://storage.haruk.in/s2600.png", img: "https://storage.haruk.in/s2600.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uzushio.html",
trainNumDistance: 3000, trainNumDistance: 3000,
info: "2600系で運転", info: "2600系で運転",
infogram: "" infogram: "",
}; };
//マリンライナー //マリンライナー
case "3104M": case "3104M":
case "3106M": case "3106M":
@ -397,11 +411,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "Rapid", type: "Rapid",
trainName: "マリンライナー", trainName: "マリンライナー",
trainIcon: "https://storage.haruk.in/s5001.png", img: "https://storage.haruk.in/s5001.png",
infoUrl: "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html", infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html",
trainNumDistance: 3100, trainNumDistance: 3100,
info: "", info: "",
infogram: "" infogram: "",
}; };
case "3102M": case "3102M":
case "3101M": case "3101M":
@ -411,22 +426,23 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "Rapid", type: "Rapid",
trainName: "マリンライナー", trainName: "マリンライナー",
trainIcon: "https://storage.haruk.in/s5001k.png", img: "https://storage.haruk.in/s5001k.png",
infoUrl: "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html", infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html",
trainNumDistance: 3100, trainNumDistance: 3100,
info: "", info: "",
infogram: "" infogram: "",
}; };
//下りサンポート //下りサンポート
case "1219M": case "1219M":
return { return {
type: "Normal", type: "Normal",
trainName: "南風リレー", trainName: "南風リレー",
trainIcon: "", img: "",
infoUrl: "", infoUrl: "",
trainNumDistance: null, trainNumDistance: null,
info: " 土曜・休日は多度津-琴平間運休", info: " 土曜・休日は多度津-琴平間運休",
infogram: "" infogram: "",
}; };
case "111M": case "111M":
@ -442,11 +458,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "Rapid", type: "Rapid",
trainName: "サンポート南風リレー", trainName: "サンポート南風リレー",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
case "5109M": case "5109M":
case "5135M": case "5135M":
@ -454,32 +470,32 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "OneManRapid", type: "OneManRapid",
trainName: "サンポート南風リレー", trainName: "サンポート南風リレー",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
case "137M": case "137M":
return { return {
type: "Rapid", type: "Rapid",
trainName: "サンポート", trainName: "サンポート",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: "土曜・休日運休", info: "土曜・休日運休",
infogram: "" infogram: "",
}; };
//上りサンポート //上りサンポート
case "116M": case "116M":
return { return {
type: "Normal", type: "Normal",
trainName: "南風リレー", trainName: "南風リレー",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
case "130M": case "130M":
@ -493,11 +509,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "Rapid", type: "Rapid",
trainName: "サンポート南風リレー", trainName: "サンポート南風リレー",
trainIcon: "", img: "",
infoUrl: "", infoUrl: "",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
case "5118M": case "5118M":
case "5120M": case "5120M":
@ -507,11 +523,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "OneManRapid", type: "OneManRapid",
trainName: "サンポート南風リレー", trainName: "サンポート南風リレー",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
//サンライズ瀬戸 //サンライズ瀬戸
@ -520,24 +536,24 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "NightLTDEXP", type: "NightLTDEXP",
trainName: "サンライズ瀬戸", trainName: "サンライズ瀬戸",
trainIcon: "https://storage.haruk.in/w285.png", img: "https://storage.haruk.in/w285.png",
infoUrl: infoUrl:
"https://www.jr-odekake.net/train/sunriseseto_izumo/index.html", "https://www.jr-odekake.net/train/sunriseseto_izumo/index.html",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "ブ" infogram: "ブ",
}; };
case "8041M": //琴平延長高松迄 case "8041M": //琴平延長高松迄
case "8031M": //琴平延長高松以降 case "8031M": //琴平延長高松以降
return { return {
type: "NightLTDEXP", type: "NightLTDEXP",
trainName: "サンライズ瀬戸", trainName: "サンライズ瀬戸",
trainIcon: "https://storage.haruk.in/w285.png", img: "https://storage.haruk.in/w285.png",
infoUrl: infoUrl:
"https://www.jr-odekake.net/train/sunriseseto_izumo/index.html", "https://www.jr-odekake.net/train/sunriseseto_izumo/index.html",
trainNumDistance: null, trainNumDistance: null,
info: "琴平延長運転日", info: "琴平延長運転日",
infogram: "ブ" infogram: "ブ",
}; };
//宇和海 //宇和海
@ -571,11 +587,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "宇和海", trainName: "宇和海",
trainIcon: "https://storage.haruk.in/s2000_uwa.png", img: "https://storage.haruk.in/s2000_uwa.png",
infoUrl: "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uwakai.html", infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/uwakai.html",
trainNumDistance: 1050, trainNumDistance: 1050,
info: "2000系で運転", info: "2000系で運転",
infogram: "" infogram: "",
}; };
//2000 アンパン込み //2000 アンパン込み
case "1058D": case "1058D":
@ -587,11 +604,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "宇和海", trainName: "宇和海",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html", infoUrl: "https://www.jr-eki.com/aptrain/naani/yosan/train.html",
trainNumDistance: 1050, trainNumDistance: 1050,
info: "アンパン列車で運転", info: "アンパン列車で運転",
infogram: "" infogram: "",
}; };
//しまんと //しまんと
case "2002D": case "2002D":
@ -601,12 +618,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "しまんと", trainName: "しまんと",
trainIcon: "https://storage.haruk.in/s2700_smn.png", img: "https://storage.haruk.in/s2700_smn.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shimanto.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/shimanto.html",
trainNumDistance: 2000, trainNumDistance: 2000,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
//あしずり 2000 //あしずり 2000
@ -621,12 +638,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "あしずり", trainName: "あしずり",
trainIcon: `https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=${TrainNumber}`, img: `https://n8n.haruk.in/webhook/anpanman-pictures.png${trainGetText}`,
infoUrl: infoUrl:
"https://www.jr-eki.com/aptrain/naani/first-generation/jikoku.html", "https://www.jr-eki.com/aptrain/naani/first-generation/jikoku.html",
trainNumDistance: 2070, trainNumDistance: 2070,
info: "2000系で運転", info: "2000系で運転",
infogram: "" infogram: "",
}; };
//あしずり 2700 //あしずり 2700
@ -637,24 +654,24 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "あしずり", trainName: "あしずり",
trainIcon: "https://storage.haruk.in/s2700_asi.png", img: "https://storage.haruk.in/s2700_asi.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ashizuri.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ashizuri.html",
trainNumDistance: 2070, trainNumDistance: 2070,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
case "2072D": case "2072D":
case "2083D": case "2083D":
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "あしずり", trainName: "あしずり",
trainIcon: "https://storage.haruk.in/s2700_asi.png", img: "https://storage.haruk.in/s2700_asi.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ashizuri.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/ashizuri.html",
trainNumDistance: 2070, trainNumDistance: 2070,
info: "2700系で運転", info: "2700系で運転",
infogram: "" infogram: "",
}; };
//剣山 //剣山
@ -668,12 +685,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "剣山", trainName: "剣山",
trainIcon: "https://storage.haruk.in/s185tu.png", img: "https://storage.haruk.in/s185tu.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/tsurugisan.html", "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/tsurugisan.html",
trainNumDistance: 4000, trainNumDistance: 4000,
info: "キハ185系で運転", info: "キハ185系で運転",
infogram: "" infogram: "",
}; };
//よしのがわトロッコ //よしのがわトロッコ
@ -682,12 +699,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "よしのがわトロッコ", trainName: "よしのがわトロッコ",
trainIcon: "https://storage.haruk.in/s185to_ai.png", img: "https://storage.haruk.in/s185to_ai.png",
infoUrl: infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/event_train/yoshino_torokko.html", "https://www.jr-shikoku.co.jp/01_trainbus/event_train/yoshino_torokko.html",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
//岡山高松アントロ //岡山高松アントロ
@ -699,12 +716,12 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "アンパンマントロッコ", trainName: "アンパンマントロッコ",
trainIcon: "https://storage.haruk.in/s32to4.png", img: "https://storage.haruk.in/s32to4.png",
infoUrl: infoUrl:
"https://www.jr-eki.com/aptrain/naani/torokko_seto/jikoku.html", "https://www.jr-eki.com/aptrain/naani/torokko_seto/jikoku.html",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
//伊予灘ものがたり //伊予灘ものがたり
@ -713,22 +730,22 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "伊予灘ものがたり", trainName: "伊予灘ものがたり",
trainIcon: "https://storage.haruk.in/s185iyor.png", img: "https://storage.haruk.in/s185iyor.png",
infoUrl: "https://iyonadamonogatari.com/", infoUrl: "https://iyonadamonogatari.com/",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
case "8092D": case "8092D":
case "8094D": case "8094D":
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "伊予灘ものがたり", trainName: "伊予灘ものがたり",
trainIcon: "https://storage.haruk.in/s185iyoy.png", img: "https://storage.haruk.in/s185iyoy.png",
infoUrl: "https://iyonadamonogatari.com/", infoUrl: "https://iyonadamonogatari.com/",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
//千年ものがたり //千年ものがたり
@ -737,11 +754,11 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "四国まんなか千年ものがたり", trainName: "四国まんなか千年ものがたり",
trainIcon: "https://storage.haruk.in/s185mm1.png", img: "https://storage.haruk.in/s185mm1.png",
infoUrl: "https://www.jr-shikoku.co.jp/sennenmonogatari/", infoUrl: "https://www.jr-shikoku.co.jp/sennenmonogatari/",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
//夜明けものがたり //夜明けものがたり
@ -752,67 +769,67 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "LTDEXP", type: "LTDEXP",
trainName: "時代の夜明けのものがたり", trainName: "時代の夜明けのものがたり",
trainIcon: "https://storage.haruk.in/s185ym1.png", img: "https://storage.haruk.in/s185ym1.png",
infoUrl: "https://www.jr-shikoku.co.jp/yoakenomonogatari/index.html", infoUrl: "https://www.jr-shikoku.co.jp/yoakenomonogatari/index.html",
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "" infogram: "",
}; };
case "9174M": case "9174M":
return { return {
type: "SPCL_Rapid", type: "SPCL_Rapid",
trainName: "マリンライナー94号", trainName: "マリンライナー94号",
trainIcon: "https://storage.haruk.in/s5001.png", img: "https://storage.haruk.in/s5001.png",
infoUrl: "https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html", infoUrl:
"https://www.jr-shikoku.co.jp/01_trainbus/vehicle-info/marine.html",
trainNumDistance: null, trainNumDistance: null,
info: "臨時列車 4/12,13,19のみ運転", info: "臨時列車 4/12,13,19のみ運転",
infogram: "" infogram: "",
}; };
case "9395D": case "9395D":
return { return {
type: "SPCL_Normal", type: "SPCL_Normal",
trainName: "", trainName: "",
trainIcon: "https://storage.haruk.in/s1500.png", img: "https://storage.haruk.in/s1500.png",
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: "臨時列車 4/12,13,19のみ運転", info: "臨時列車 4/12,13,19のみ運転",
infogram: "" infogram: "",
}; };
case "9662D": case "9662D":
case "9665D": case "9665D":
return { return {
type: "SPCL_Normal", type: "SPCL_Normal",
trainName: "れんげ号", trainName: "れんげ号",
trainIcon: "", img: "",
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: "臨時列車 4/29のみ運転", info: "臨時列車 4/29のみ運転",
infogram: "" infogram: "",
}; };
case "9664D": case "9664D":
case "9663D": case "9663D":
return { return {
type: "SPCL_Normal", type: "SPCL_Normal",
trainName: "わらぐろ号", trainName: "わらぐろ号",
trainIcon: "", img: "",
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: "臨時列車 4/29のみ運転", info: "臨時列車 4/29のみ運転",
infogram: "" infogram: "",
}; };
default: default:
if(getJRF(TrainNumber) !== null){ if (getJRF(TrainNumber) !== null) {
return { return {
type: "Freight", type: "Freight",
trainName: getJRF(TrainNumber), trainName: getJRF(TrainNumber),
trainIcon: "https://storage.haruk.in/ef210a.png", img: "https://storage.haruk.in/ef210a.png",
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: "", info: "",
infogram: "", infogram: "",
}; };
} } else if (
else if (
new RegExp(/^4[1-9]\d\d[DM]$/).test(TrainNumber) || new RegExp(/^4[1-9]\d\d[DM]$/).test(TrainNumber) ||
new RegExp(/^5[1-7]\d\d[DM]$/).test(TrainNumber) || new RegExp(/^5[1-7]\d\d[DM]$/).test(TrainNumber) ||
new RegExp(/^3[2-9]\d\d[DM]$/).test(TrainNumber) new RegExp(/^3[2-9]\d\d[DM]$/).test(TrainNumber)
@ -820,36 +837,36 @@ export const customTrainDataDetector = (TrainNumber: string) => {
return { return {
type: "OneMan", type: "OneMan",
trainName: "", trainName: "",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
else if ( else if (
new RegExp(/^[1-9]\d\d[DM]$/).test(TrainNumber) || new RegExp(/^[1-9]\d\d[DM]$/).test(TrainNumber) ||
new RegExp(/^1[26]\d\d[DM]$/).test(TrainNumber) || new RegExp(/^1[26]\d\d[DM]$/).test(TrainNumber) ||
new RegExp(/^58\d\d[DM]$/).test(TrainNumber) || new RegExp(/^58\d\d[DM]$/).test(TrainNumber) ||
new RegExp(/^6\d\d\d[DM]$/).test(TrainNumber) new RegExp(/^6\d\d\d[DM]$/).test(TrainNumber)
){ ) {
return { return {
type: "Normal", type: "Normal",
trainName: "", trainName: "",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
} else { } else {
return { return {
type: "Other", type: "Other",
trainName: "", trainName: "",
trainIcon: null, img: null,
infoUrl: null, infoUrl: null,
trainNumDistance: null, trainNumDistance: null,
info: null, info: null,
infogram: "" infogram: "",
}; };
} }
} }

View File

@ -4,12 +4,13 @@ import MapView from "react-native-maps";
import { useCurrentTrain } from "../stateBox/useCurrentTrain"; import { useCurrentTrain } from "../stateBox/useCurrentTrain";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import lineColorList from "../assets/originData/lineColorList"; import lineColorList from "../assets/originData/lineColorList";
import { lineListPair, stationIDPair } from "../lib/getStationList"; import { lineList_LineWebID, lineListPair, stationIDPair } from "../lib/getStationList";
import { SheetManager } from "react-native-actions-sheet"; import { SheetManager } from "react-native-actions-sheet";
import { useTrainMenu } from "../stateBox/useTrainMenu"; import { useTrainMenu } from "../stateBox/useTrainMenu";
import { MapPin } from "./TrainMenu/MapPin"; import { MapPin } from "./TrainMenu/MapPin";
import { UsefulBox } from "./TrainMenu/UsefulBox"; import { UsefulBox } from "./TrainMenu/UsefulBox";
import { MapsButton } from "./TrainMenu/MapsButton"; import { MapsButton } from "./TrainMenu/MapsButton";
import { useStationList } from "@/stateBox/useStationList";
export default function TrainMenu({ style }) { export default function TrainMenu({ style }) {
const { webview } = useCurrentTrain(); const { webview } = useCurrentTrain();
const mapRef = useRef(); const mapRef = useRef();
@ -20,31 +21,28 @@ export default function TrainMenu({ style }) {
setSelectedLine, setSelectedLine,
mapsStationData: stationData, mapsStationData: stationData,
} = useTrainMenu(); } = useTrainMenu();
const { originalStationList } = useStationList();
useEffect(() => { useEffect(() => {
const stationPinData = []; const stationPinData = [];
Object.keys(stationData).forEach((d, indexBase) => { Object.keys(lineList_LineWebID).forEach((d, indexBase) => {
stationData[d].forEach((D, index) => { originalStationList[d].forEach((D, index) => {
if (!D.StationMap) return null; if (selectedLine && selectedLine != lineList_LineWebID[d]) return;
if (selectedLine && selectedLine != d) return; const latlng = [D.lat,D.lng];
const latlng = D.StationMap.replace(
"https://www.google.co.jp/maps/place/",
""
).split(",");
if (latlng.length == 0) return null; if (latlng.length == 0) return null;
stationPinData.push({ D, d, latlng, indexBase: 0, index }); stationPinData.push({ D, d, latlng, indexBase: 0, index });
}); });
}); });
setStationPin(stationPinData); setStationPin(stationPinData);
}, [stationData, selectedLine]); }, [originalStationList, selectedLine]);
useLayoutEffect(() => { useLayoutEffect(() => {
mapRef.current.fitToCoordinates( mapRef?.current.fitToCoordinates(
stationPin.map(({ latlng }) => ({ stationPin.map(({ latlng }) => ({
latitude: parseFloat(latlng[0]), latitude: parseFloat(latlng[0]),
longitude: parseFloat(latlng[1]), longitude: parseFloat(latlng[1]),
})), })),
{ edgePadding: { top: 80, bottom: 120, left: 50, right: 50 } } // Add margin values here { edgePadding: { top: 80, bottom: 120, left: 50, right: 50 } } // Add margin values here
); );
}, [stationPin]); }, [stationPin,mapRef]);
return ( return (
<View style={{ height: "100%", backgroundColor: "#0099CC", ...style }}> <View style={{ height: "100%", backgroundColor: "#0099CC", ...style }}>
<MapView <MapView
@ -72,7 +70,7 @@ export default function TrainMenu({ style }) {
d={d} d={d}
navigate={navigate} navigate={navigate}
webview={webview} webview={webview}
key={D.StationNumber + d} key={D.Station_JP + D.StationNumber + d}
/> />
))} ))}
</MapView> </MapView>
@ -163,6 +161,7 @@ export default function TrainMenu({ style }) {
}); });
}); });
}} }}
key={stationIDPair[d]}
> >
<Text <Text
style={{ color: "white", fontWeight: "bold", fontSize: 20 }} style={{ color: "white", fontWeight: "bold", fontSize: 20 }}

View File

@ -16,6 +16,9 @@ import { TrainPosition } from "./LED_inside_Component/TrainPosition";
import { TrainPositionDataPush } from "./LED_inside_Component/TrainPositionDataPush"; import { TrainPositionDataPush } from "./LED_inside_Component/TrainPositionDataPush";
import { TrainPositionDataDelete } from "./LED_inside_Component/TrainPositionDataDelete"; import { TrainPositionDataDelete } from "./LED_inside_Component/TrainPositionDataDelete";
import { useStationList } from "../../stateBox/useStationList"; import { useStationList } from "../../stateBox/useStationList";
import useInterval from "@/lib/useInterval";
import dayjs from "dayjs";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
type Props = { type Props = {
d: { d: {
@ -43,6 +46,7 @@ export const EachData: FC<Props> = (props) => {
} = props; } = props;
const { currentTrain } = useCurrentTrain(); const { currentTrain } = useCurrentTrain();
const { stationList } = useStationList(); const { stationList } = useStationList();
const { allCustonTrainData } = useAllTrainDiagram();
const openTrainInfo = (d: { const openTrainInfo = (d: {
train: string; train: string;
lastStation: string; lastStation: string;
@ -72,7 +76,10 @@ export const EachData: FC<Props> = (props) => {
}; };
const getTrainDataFromCurrentTrain = (trainNum: string) => { const getTrainDataFromCurrentTrain = (trainNum: string) => {
const customTrainData = customTrainDataDetector(d.train); const customTrainData = customTrainDataDetector(
d.train,
allCustonTrainData
);
switch (customTrainData.type) { switch (customTrainData.type) {
case "Normal": case "Normal":
case "OneMan": case "OneMan":
@ -117,26 +124,47 @@ export const EachData: FC<Props> = (props) => {
const [descInput, setDescInput] = useState(""); const [descInput, setDescInput] = useState("");
const [stationInput, setStationInput] = useState(""); const [stationInput, setStationInput] = useState("");
const [stationNumberInput, setStationNumberInput] = useState(""); const [stationNumberInput, setStationNumberInput] = useState("");
const [isShow, setIsShow] = useState(true);
const [isDepartureNow, setIsDepartureNow] = useState(false);
useEffect(() => {
const currentTime = dayjs();
const trainTime = currentTime
.set("hour", parseInt(d.time.split(":")[0]))
.set("minute", parseInt(d.time.split(":")[1]));
const diff = trainTime.diff(currentTime, "minute");
if (diff < 2) setIsDepartureNow(true);
else setIsDepartureNow(false);
return () => {
setIsDepartureNow(false);
setIsShow(true);
};
}, [d.time, currentTrainData]);
useInterval(() => {
if (isDepartureNow) {
setIsShow(!isShow);
}
}, 800);
return ( return (
<> <>
<TrainPositionDataDelete <TrainPositionDataDelete
dialog={deleteDialog} dialog={deleteDialog}
setDialog={setDeleteDialog} setDialog={setDeleteDialog}
currentTrainData={currentTrainData} {...{ currentTrainData, stationInput, stationNumberInput }}
stationInput={stationInput}
stationNumberInput={stationNumberInput}
/> />
<TrainPositionDataPush <TrainPositionDataPush
dialog={dialog} dialog={dialog}
setDialog={setDialog} setDialog={setDialog}
currentTrainData={currentTrainData} {...{
stationInput={stationInput} currentTrainData,
stationNumberInput={stationNumberInput} stationInput,
posInput={posInput} stationNumberInput,
descInput={descInput} posInput,
setPosInput={setPosInput} descInput,
setDescInput={setDescInput} setPosInput,
station={station} setDescInput,
station,
}}
/> />
<TouchableOpacity <TouchableOpacity
style={{ style={{
@ -147,8 +175,10 @@ export const EachData: FC<Props> = (props) => {
marginHorizontal: "3%", marginHorizontal: "3%",
backgroundColor: "#000", backgroundColor: "#000",
flexDirection: "row", flexDirection: "row",
opacity: isShow ? 1 : 0.5,
}} }}
onPress={() => openTrainInfo(d)} onPress={() => openTrainInfo(d)}
key={d.train + "-eachData"}
> >
<TrainName <TrainName
trainName={train.trainName} trainName={train.trainName}
@ -161,6 +191,16 @@ export const EachData: FC<Props> = (props) => {
<DependTime time={d.time} /> <DependTime time={d.time} />
<StatusAndDelay trainDelayStatus={trainDelayStatus} /> <StatusAndDelay trainDelayStatus={trainDelayStatus} />
</TouchableOpacity> </TouchableOpacity>
{!!isDepartureNow && (
<Description
info={
d.lastStation == "当駅止"
? "この列車は当駅止です。間もなく到着します。"
: "列車の出発時刻です。"
}
key={d.train + "-description"}
/>
)}
{trainDescriptionSwitch && ( {trainDescriptionSwitch && (
<TrainPosition <TrainPosition
trainIDSwitch={trainIDSwitch} trainIDSwitch={trainIDSwitch}
@ -179,7 +219,7 @@ export const EachData: FC<Props> = (props) => {
/> />
)} )}
{trainDescriptionSwitch && !!train.info && ( {trainDescriptionSwitch && !!train.info && (
<Description info={train.info} /> <Description info={train.info} key={d.train + "-description"} />
)} )}
</> </>
); );

View File

@ -1,10 +1,19 @@
import { View, Text } from "react-native"; import { View, Text } from "react-native";
import { useCurrentTrain } from "../../../stateBox/useCurrentTrain"; import { useCurrentTrain } from "../../../stateBox/useCurrentTrain";
import LottieView from "lottie-react-native"; import LottieView from "lottie-react-native";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons, AntDesign } from "@expo/vector-icons";
import { useStationList } from "@/stateBox/useStationList";
import { useNavigation } from "@react-navigation/native";
export const Header = ({ getCurrentTrain }) => { export const Header = ({ station }) => {
const { currentTrainLoading, setCurrentTrainLoading } = useCurrentTrain(); const {
currentTrainLoading,
setCurrentTrainLoading,
getCurrentTrain,
inject,
} = useCurrentTrain();
const { getInjectJavascriptAddress } = useStationList();
const { navigate } = useNavigation();
return ( return (
<View <View
style={{ style={{
@ -15,7 +24,8 @@ export const Header = ({ getCurrentTrain }) => {
flexDirection: "row", flexDirection: "row",
}} }}
> >
<View style={{ flex: 1 }}></View> <View style={{ flex: 1, flexDirection: "column", alignItems: "center" }}>
</View>
<View style={{}}> <View style={{}}>
<Text style={{ fontSize: 25, color: "white", fontWeight: "bold" }}> <Text style={{ fontSize: 25, color: "white", fontWeight: "bold" }}>
次の列車 次の列車
@ -45,4 +55,4 @@ export const Header = ({ getCurrentTrain }) => {
</View> </View>
</View> </View>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { View } from "react-native"; import { View, useWindowDimensions } from "react-native";
import { widthPercentageToDP as wp } from "react-native-responsive-screen"; import dayjs from "dayjs";
import { useInterval } from "../../lib/useInterval"; import { useInterval } from "../../lib/useInterval";
import { objectIsEmpty } from "../../lib/objectIsEmpty"; import { objectIsEmpty } from "../../lib/objectIsEmpty";
import { useCurrentTrain } from "../../stateBox/useCurrentTrain"; import { useCurrentTrain } from "../../stateBox/useCurrentTrain";
@ -10,8 +10,8 @@ import { Footer } from "./LED_Vision_Component/Footer";
import { Header } from "./LED_Vision_Component/Header"; import { Header } from "./LED_Vision_Component/Header";
import { Description } from "./LED_inside_Component/Description"; import { Description } from "./LED_inside_Component/Description";
import { EachData } from "./EachData"; import { EachData } from "./EachData";
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
import { trainPosition } from "@/lib/trainPositionTextArray";
/** /**
* *
@ -43,13 +43,7 @@ import { EachData } from "./EachData";
* 9062D 四国まんなか千年ものがたり(臨時) * 9062D 四国まんなか千年ものがたり(臨時)
*/ */
export default function LED_vision(props) { export default function LED_vision(props) {
const { const { station, navigate, openStationACFromEachTrainInfo } = props;
station,
trainDiagram,
getCurrentTrain,
navigate,
openStationACFromEachTrainInfo,
} = props;
const { currentTrain } = useCurrentTrain(); const { currentTrain } = useCurrentTrain();
const [stationDiagram, setStationDiagram] = useState({}); //当該駅の全時刻表 const [stationDiagram, setStationDiagram] = useState({}); //当該駅の全時刻表
const [finalSwitch, setFinalSwitch] = useState(false); const [finalSwitch, setFinalSwitch] = useState(false);
@ -57,36 +51,40 @@ export default function LED_vision(props) {
const [trainDescriptionSwitch, setTrainDescriptionSwitch] = useState(false); const [trainDescriptionSwitch, setTrainDescriptionSwitch] = useState(false);
const [isInfoArea, setIsInfoArea] = useState(false); const [isInfoArea, setIsInfoArea] = useState(false);
const { areaInfo, areaStationID } = useAreaInfo(); const { areaInfo, areaStationID } = useAreaInfo();
const { allTrainDiagram } = useAllTrainDiagram();
useEffect(() => { useEffect(() => {
AS.getItem("LEDSettings/trainIDSwitch").then((data) => { AS.getItem("LEDSettings/trainIDSwitch").then((data) => {
setTrainIDSwitch(data === "true"); setTrainIDSwitch(data === "true");
}); });
AS.getItem("LEDSettings/trainDescriptionSwitch").then((data) => { AS.getItem("LEDSettings/trainDescriptionSwitch").then((data) => {
setTrainDescriptionSwitch(data == "true"); setTrainDescriptionSwitch(data === "true");
}); });
AS.getItem("LEDSettings/finalSwitch").then((data) => { AS.getItem("LEDSettings/finalSwitch").then((data) => {
setFinalSwitch(data == "true"); setFinalSwitch(data === "true");
}); });
}, []); }, []);
useEffect(() => { useEffect(() => {
// 現在の駅に停車するダイヤを作成する副作用[列車ダイヤと現在駅情報] // 現在の駅に停車するダイヤを作成する副作用[列車ダイヤと現在駅情報]
if (!trainDiagram) { if (!allTrainDiagram) {
setStationDiagram({}); setStationDiagram({});
return; return;
} }
let returnData = {}; let returnData = {};
Object.keys(trainDiagram).forEach((key) => { Object.keys(allTrainDiagram).forEach((key) => {
if (trainDiagram[key].match(station[0].Station_JP + ",")) { if (allTrainDiagram[key].match(station[0].Station_JP + ",")) {
returnData[key] = trainDiagram[key]; returnData[key] = allTrainDiagram[key];
} }
}); });
setStationDiagram(returnData); setStationDiagram(returnData);
setIsInfoArea(station.some((s) => areaStationID.includes(s.StationNumber))); setIsInfoArea(station.some((s) => areaStationID.includes(s.StationNumber)));
}, [trainDiagram, station]); }, [allTrainDiagram, station]);
const [trainTimeAndNumber, setTrainTimeAndNumber] = useState(null); /*
{lastStation: "当駅止", time: "12:34", train: "1234M"}
*/
const [trainTimeAndNumber, setTrainTimeAndNumber] = useState([]);
useEffect(() => { useEffect(() => {
//現在の駅に停車する列車から時刻を切り出してLEDベースにフォーマット //現在の駅に停車する列車から時刻を切り出してLEDベースにフォーマット
@ -100,14 +98,15 @@ export default function LED_vision(props) {
if (!trainTimeAndNumber) return () => {}; if (!trainTimeAndNumber) return () => {};
if (!currentTrain) return () => {}; if (!currentTrain) return () => {};
const data = trainTimeAndNumber const data = trainTimeAndNumber
.filter((d) => currentTrain.map((m) => m.num).includes(d.train)) .filter((d) => currentTrain.map((m) => m.num).includes(d.train)) //現在の列車に絞る[ToDo]
.filter(timeFiltering) .filter(timeFiltering)
.filter((d) => !!finalSwitch || d.lastStation != "当駅止"); .filter((d) => !!finalSwitch || d.lastStation != "当駅止");
setSelectedTrain(data); setSelectedTrain(data);
}, [trainTimeAndNumber, currentTrain, finalSwitch]); }, [trainTimeAndNumber, currentTrain, finalSwitch]);
const getTime = (stationDiagram, station) => { const getTime = (stationDiagram, station) => {
const returnData = Object.keys(stationDiagram).map((trainNum) => { const returnData = Object.keys(stationDiagram)
.map((trainNum) => {
let trainData = {}; let trainData = {};
stationDiagram[trainNum].split("#").forEach((data) => { stationDiagram[trainNum].split("#").forEach((data) => {
if (data.match("着")) { if (data.match("着")) {
@ -116,7 +115,7 @@ export default function LED_vision(props) {
if (data.split(",")[0] === station.Station_JP) { if (data.split(",")[0] === station.Station_JP) {
if (data.match(",発,")) { if (data.match(",発,")) {
trainData.time = data.split(",発,")[1]; trainData.time = data.split(",発,")[1];
} else if(data.match(",着,")){ } else if (data.match(",着,")) {
trainData.time = data.split(",着,")[1]; trainData.time = data.split(",着,")[1];
trainData.lastStation = "当駅止"; trainData.lastStation = "当駅止";
} }
@ -127,7 +126,8 @@ export default function LED_vision(props) {
time: trainData.time, time: trainData.time,
lastStation: trainData.lastStation, lastStation: trainData.lastStation,
}; };
}).filter((d) => d.time); })
.filter((d) => d.time);
return returnData.sort((a, b) => { return returnData.sort((a, b) => {
switch (true) { switch (true) {
case parseInt(a.time.split(":")[0]) < parseInt(b.time.split(":")[0]): case parseInt(a.time.split(":")[0]) < parseInt(b.time.split(":")[0]):
@ -145,16 +145,40 @@ export default function LED_vision(props) {
}; };
const timeFiltering = (d) => { const timeFiltering = (d) => {
const date = new Date();
const newDate = new Date(); const baseTime = 2;
let data = d.time.split(":");
if (currentTrain.filter((t) => t.num == d.train).length == 0) {
const date = dayjs();
const trainTime = date
.hour(parseInt(d.time.split(":")[0]))
.minute(parseInt(d.time.split(":")[1]));
if (date.isAfter(trainTime)) {
return false;
} else if (trainTime.diff(date) < baseTime * 60 * 60 * 1000) {
return true;
}
return false;
} else {
const Pos = trainPosition(
currentTrain.filter((t) => t.num == d.train)[0]
);
const nextPos = Pos.isBetween ? Pos.Pos.to : Pos.Pos.Pos;
const PrePos = Pos.isBetween ? Pos.Pos.from : "";
if (station[0].Station_JP == nextPos) {
if(d.lastStation != "当駅止") return true;
} else if (station[0].Station_JP == PrePos) {
return false;
}
const date = dayjs();
let [h, m] = d.time.split(":");
let delay = isNaN(currentTrain.filter((t) => t.num == d.train)[0].delay) let delay = isNaN(currentTrain.filter((t) => t.num == d.train)[0].delay)
? 0 ? 0
: currentTrain.filter((t) => t.num == d.train)[0].delay; : currentTrain.filter((t) => t.num == d.train)[0].delay;
const db = date.hour(parseInt(h)).minute(parseInt(m) + parseInt(delay));
date.setHours(parseInt(data[0])); return !date.isAfter(db);
date.setMinutes(parseInt(data[1]) + parseInt(delay)); }
return !(newDate > date);
}; };
const [areaString, setAreaString] = useState(""); const [areaString, setAreaString] = useState("");
@ -186,18 +210,19 @@ export default function LED_vision(props) {
} }
setAreaStringLength(areaInfo.length); setAreaStringLength(areaInfo.length);
}, [areaInfo]); }, [areaInfo]);
const { width } = useWindowDimensions();
const adjustedWidth = width * 0.98;
return ( return (
<View <View
style={{ style={{
width: wp("98%"), width: adjustedWidth,
/* height: wp("98%")/10*9, */ backgroundColor: "#432", /* height: wp("98%")/10*9, */ backgroundColor: "#432",
borderWidth: 1, borderWidth: 1,
margin: 10, margin: 10,
marginHorizontal: wp("1%"), marginHorizontal: width * 0.01,
}} }}
> >
<Header getCurrentTrain={getCurrentTrain} /> <Header station={station[0]} />
{selectedTrain.map((d) => ( {selectedTrain.map((d) => (
<EachData <EachData
{...{ {...{

View File

@ -1,9 +1,22 @@
import React, { CSSProperties, FC } from "react"; import React, { CSSProperties, FC } from "react";
import { widthPercentageToDP as wp } from "react-native-responsive-screen"; import {
import { Platform, Text, TextStyle, View, ViewStyle } from "react-native"; Platform,
Text,
TextStyle,
useWindowDimensions,
View,
ViewStyle,
} from "react-native";
import { StationName } from "./StationName"; import { StationName } from "./StationName";
import lineColorList from "../../assets/originData/lineColorList"; import lineColorList from "../../assets/originData/lineColorList";
export const NextPreStationLine = ({ nexStation, preStation, isMatsuyama }) => { export const NextPreStationLine = ({ nexStation, preStation, isMatsuyama }) => {
const 下枠フレーム: ViewStyle = {
flex: 1,
flexDirection: "row",
alignContent: "center",
alignItems: "center",
};
return ( return (
<View style={}> <View style={}>
<View style={}> <View style={}>
@ -49,12 +62,40 @@ type FCimport = {
children: string; children: string;
}; };
const BottomSideArrow: FC<FCimport> = ({ isMatsuyama, children }) => { const BottomSideArrow: FC<FCimport> = ({ isMatsuyama, children }) => {
const 下枠左右マーク: TextStyle = {
fontWeight: "bold",
fontSize: parseInt("20%"),
color: "white",
paddingHorizontal: 5,
textAlignVertical: "center",
};
return !isMatsuyama && <Text style={}>{children}</Text>; return !isMatsuyama && <Text style={}>{children}</Text>;
}; };
const BottomStationNumberView: FC<FCimport> = ({ isMatsuyama, children }) => { const BottomStationNumberView: FC<FCimport> = ({ isMatsuyama, children }) => {
const { width } = useWindowDimensions();
const lineID = children.slice(0, 1); const lineID = children.slice(0, 1);
const lineName = children.slice(1); const lineName = children.slice(1);
const 下枠駅ナンバー: ViewStyle = {
alignContent: "center",
alignItems: "center",
width: width * 0.08,
height: width * 0.08,
margin: width * 0.01,
backgroundColor: "white",
borderWidth: parseInt("3%"),
borderRadius: parseInt("100%"),
};
const 下枠駅ナンバーB: ViewStyle = {
alignContent: "center",
alignItems: "center",
width: width * 0.07,
height: width * 0.07,
margin: width * 0.02,
backgroundColor: "white",
borderWidth: parseInt("3%"),
borderRadius: parseInt("100%"),
};
return ( return (
<View <View
style={{ style={{
@ -77,37 +118,3 @@ const BottomStationNumberView: FC<FCimport> = ({ isMatsuyama, children }) => {
</View> </View>
); );
}; };
const 下枠フレーム: ViewStyle = {
flex: 1,
flexDirection: "row",
alignContent: "center",
alignItems: "center",
};
const 下枠左右マーク: TextStyle = {
fontWeight: "bold",
fontSize: parseInt("20%"),
color: "white",
paddingHorizontal: 5,
textAlignVertical: "center",
};
const 下枠駅ナンバー: ViewStyle = {
alignContent: "center",
alignItems: "center",
width: wp("8%"),
height: wp("8%"),
margin: wp("1%"),
backgroundColor: "white",
borderWidth: parseInt("3%"),
borderRadius: parseInt("100%"),
};
const 下枠駅ナンバーB: ViewStyle = {
alignContent: "center",
alignItems: "center",
width: wp("7%"),
height: wp("7%"),
margin: wp("2%"),
backgroundColor: "white",
borderWidth: parseInt("3%"),
borderRadius: parseInt("100%"),
};

View File

@ -1,6 +1,10 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { View, Text, TouchableOpacity } from "react-native"; import {
import { widthPercentageToDP as wp } from "react-native-responsive-screen"; View,
Text,
TouchableOpacity,
useWindowDimensions,
} from "react-native";
import { MaterialCommunityIcons } from "@expo/vector-icons"; import { MaterialCommunityIcons } from "@expo/vector-icons";
import LottieView from "lottie-react-native"; import LottieView from "lottie-react-native";
import { useInterval } from "../../lib/useInterval"; import { useInterval } from "../../lib/useInterval";
@ -15,7 +19,14 @@ import { AddressText } from "./AddressText";
import { useStationList } from "../../stateBox/useStationList"; import { useStationList } from "../../stateBox/useStationList";
export default function Sign(props) { export default function Sign(props) {
const { currentStation, oP, oLP, isCurrentStation = false } = props; const { oP, oLP, isCurrentStation = false, stationID } = props;
const { width, height } = useWindowDimensions();
const { getStationDataFromId } = useStationList();
if (!stationID) {
return <></>;
}
const [currentStationData] = useState(getStationDataFromId(stationID));
const { favoriteStation, setFavoriteStation } = useFavoriteStation(); const { favoriteStation, setFavoriteStation } = useFavoriteStation();
const [nexPrePosition, setNexPrePosition] = useState(0); const [nexPrePosition, setNexPrePosition] = useState(0);
const { originalStationList } = useStationList(); const { originalStationList } = useStationList();
@ -26,48 +37,40 @@ export default function Sign(props) {
useLayoutEffect(() => { useLayoutEffect(() => {
const isFavorite = favoriteStation.filter((d) => { const isFavorite = favoriteStation.filter((d) => {
const compare = JSON.stringify(d); const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation); const current = JSON.stringify(currentStationData);
if (compare === current) { return compare === current;
return true;
} else {
return false;
}
}); });
setTestButtonStatus(isFavorite.length == 0 ? false : true); setTestButtonStatus(isFavorite.length == 0 ? false : true);
}, [favoriteStation, currentStation]); }, [favoriteStation, currentStationData]);
useEffect(() => { useEffect(() => {
const isFavorite = favoriteStation.filter((d) => { const isFavorite = favoriteStation.filter((d) => {
const compare = JSON.stringify(d); const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation); const current = JSON.stringify(currentStationData);
if (compare === current) { return compare === current;
return true;
} else {
return false;
}
}); });
setTestButtonStatus(isFavorite.length == 0 ? false : true); setTestButtonStatus(isFavorite.length == 0 ? false : true);
}, [favoriteStation, currentStation]); }, [favoriteStation, currentStationData]);
useInterval(() => { useInterval(() => {
if (currentStation.length == 1) { if (currentStationData.length == 1) {
setNexPrePosition(0); setNexPrePosition(0);
return () => {}; return () => {};
} }
setNexPrePosition( setNexPrePosition(
nexPrePosition + 1 == currentStation.length ? 0 : nexPrePosition + 1 nexPrePosition + 1 == currentStationData.length ? 0 : nexPrePosition + 1
); );
}, 2000); }, 2000);
useEffect(() => { useEffect(() => {
setNexPrePosition(0); setNexPrePosition(0);
getPreNextStation(currentStation[0]); getPreNextStation(currentStationData[0]);
if (currentStation.length == 1) return () => {}; if (currentStationData.length == 1) return () => {};
getPreNextStation(currentStation[1]); getPreNextStation(currentStationData[1]);
}, [currentStation]); }, [currentStationData]);
useEffect(() => { useEffect(() => {
if (!currentStation[nexPrePosition]) return () => {}; if (!currentStationData[nexPrePosition]) return () => {};
getPreNextStation(currentStation[nexPrePosition]); getPreNextStation(currentStationData[nexPrePosition]);
}, [nexPrePosition]); }, [nexPrePosition]);
const getPreNextStation = (now) => { const getPreNextStation = (now) => {
const lineList = [ const lineList = [
@ -101,84 +104,37 @@ export default function Sign(props) {
if (returnData[1]) setNexStation(returnData[1]); if (returnData[1]) setNexStation(returnData[1]);
} }
}; };
const isMatsuyama = currentStation[0].StationNumber == "Y55"; const isMatsuyama = currentStationData[0].StationNumber == "Y55";
//const isMatsuyama = true; //const isMatsuyama = true;
const favoliteChanger = () => { const favoliteChanger = () => {
if (testButtonStatus) { if (testButtonStatus) {
const otherData = favoriteStation.filter((d) => { const otherData = favoriteStation.filter((d) => {
const compare = JSON.stringify(d); const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation); const current = JSON.stringify(currentStationData);
return compare !== current; return compare !== current;
}); });
AS.setItem("favoriteStation", JSON.stringify(otherData)); AS.setItem("favoriteStation", JSON.stringify(otherData));
setFavoriteStation(otherData); setFavoriteStation(otherData);
} else { } else {
let ret = favoriteStation; let ret = favoriteStation;
ret.push(currentStation); ret.push(currentStationData);
AS.setItem("favoriteStation", JSON.stringify(ret)); AS.setItem("favoriteStation", JSON.stringify(ret));
setFavoriteStation(ret); setFavoriteStation(ret);
} }
setTestButtonStatus(!testButtonStatus); setTestButtonStatus(!testButtonStatus);
}; };
return (
<TouchableOpacity
style={styleSheet[isMatsuyama ? "外枠B" : "外枠"]}
onPress={oP}
onLongPress={oLP}
>
{isMatsuyama && (
<LottieView
autoPlay
loop
style={{
width: wp("80%"),
height: (wp("80%") / 20) * 9,
backgroundColor: "#fff",
}}
source={require("../../assets/StationSign.json")}
/>
)}
<StationNumberMaker {...{ currentStation, isMatsuyama }} />
<StationNameArea {...{ currentStation, isMatsuyama }} />
{isCurrentStation ? (
<View style={{ position: "absolute", right: 0, top: 0 }}>
<MaterialCommunityIcons
name="crosshairs-gps"
style={{ padding: 5 }}
color="#0099CC"
size={30}
/>
</View>
) : (
<TouchableOpacity
style={{ position: "absolute", right: -15, top: -20 }}
onPress={favoliteChanger}
>
<LottieDelayView progress={testButtonStatus ? 1 : 0} />
</TouchableOpacity>
)}
<Text style={styleSheet.JRStyle}>JR</Text> const styleSheet = {
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"]} />
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"]}>
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
</View>
<AddressText {...{ currentStation, isMatsuyama }} />
</TouchableOpacity>
);
}
const styleSheet = {
外枠: { 外枠: {
width: wp("80%"), width: width * 0.8,
height: (wp("80%") / 20) * 9, height: ((width * 0.8) / 20) * 9,
borderColor: "#0099CC", borderColor: "#0099CC",
borderWidth: 1, borderWidth: 1,
backgroundColor: "white", backgroundColor: "white",
}, },
外枠B: { 外枠B: {
width: wp("80%"), width: width * 0.8,
height: (wp("80%") / 20) * 9, height: ((width * 0.8) / 20) * 9,
borderWidth: 0, borderWidth: 0,
}, },
下帯: { 下帯: {
@ -221,4 +177,56 @@ const styleSheet = {
alignItems: "center", alignItems: "center",
flexDirection: "column", flexDirection: "column",
}, },
}; };
return (
<TouchableOpacity
style={styleSheet[isMatsuyama ? "外枠B" : "外枠"]}
onPress={oP}
onLongPress={oLP}
>
{isMatsuyama && (
<LottieView
autoPlay
loop
style={{
width: width * 0.8,
height: ((width * 0.8) / 20) * 9,
backgroundColor: "#fff",
}}
source={require("../../assets/StationSign.json")}
/>
)}
<StationNumberMaker
{...{ currentStation: currentStationData, isMatsuyama }}
/>
<StationNameArea
{...{ currentStation: currentStationData, isMatsuyama }}
/>
{isCurrentStation ? (
<View style={{ position: "absolute", right: 0, top: 0 }}>
<MaterialCommunityIcons
name="crosshairs-gps"
style={{ padding: 5 }}
color="#0099CC"
size={30}
/>
</View>
) : (
<TouchableOpacity
style={{ position: "absolute", right: -15, top: -20 }}
onPress={favoliteChanger}
>
<LottieDelayView progress={testButtonStatus ? 1 : 0} />
</TouchableOpacity>
)}
<Text style={styleSheet.JRStyle}>JR</Text>
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"]} />
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"]}>
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
</View>
<AddressText {...{ currentStation: currentStationData, isMatsuyama }} />
</TouchableOpacity>
);
}

View File

@ -1,10 +1,11 @@
import React from "react"; import React from "react";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import { widthPercentageToDP as wp } from "react-native-responsive-screen"; import { useWindowDimensions } from "react-native";
import lineColorList from "../../assets/originData/lineColorList"; import lineColorList from "../../assets/originData/lineColorList";
export const StationNumberMaker = (props) => { export const StationNumberMaker = (props) => {
const { currentStation, isMatsuyama } = props; const { currentStation, isMatsuyama } = props;
const { width } = useWindowDimensions();
const getTop = (array: number[], index: number) => { const getTop = (array: number[], index: number) => {
if (array.length == 1) return 20; if (array.length == 1) return 20;
else if (index == 0) return 5; else if (index == 0) return 5;
@ -24,8 +25,8 @@ export const StationNumberMaker = (props) => {
alignItems: "center", alignItems: "center",
top: `${getTop(array, index)}%`, top: `${getTop(array, index)}%`,
right: "10%", right: "10%",
width: wp("10%"), width: (width / 100 * 10),
height: wp("10%"), height: (width / 100 * 10),
borderColor: lineColorList[lineID], borderColor: lineColorList[lineID],
backgroundColor: "white", backgroundColor: "white",
borderWidth: parseInt("3%"), borderWidth: parseInt("3%"),

View File

@ -1 +1 @@
export const news = "2025-03-06"; export const news = "2025-07-09";

View File

@ -40,6 +40,17 @@ export const lineListPair = {
N: "鳴門線(池谷-鳴門間)[N]", N: "鳴門線(池谷-鳴門間)[N]",
M: "瀬戸大橋線(児島-宇多津間)[M]", M: "瀬戸大橋線(児島-宇多津間)[M]",
}; };
export const lineList_LineWebID = {
"予讃線(高松-松山間)[Y]" : "yosan",
"予讃線(松山-宇和島間)[U]" : "uwajima",
"予讃線/愛ある伊予灘線(向井原-伊予大洲間)[S]" : "uwajima2",
"土讃線(多度津-高知間)[D]" : "dosan",
"土讃線(高知-窪川間)[K]" : "dosan2",
"高徳線(高松-徳島間)[T]" : "koutoku",
"徳島線(徳島-阿波池田間)[B]" : "tokushima",
"鳴門線(池谷-鳴門間)[N]" : "naruto",
"瀬戸大橋線(児島-宇多津間)[M]" : "seto",
};
export const getStationList2 = async () => { export const getStationList2 = async () => {
return { return {
yosan, yosan,
@ -167,6 +178,11 @@ export const getStationList = async () => {
, ,
stationList["日英対応表"] stationList["日英対応表"]
); );
stationList["予讃線(松山-宇和島間)[U]"] = addStationPosition(
concatBetweenStations(stationList["予讃線(松山-宇和島間)[U]"]),
,
stationList["日英対応表"]
);
stationList["予讃線/愛ある伊予灘線(向井原-伊予大洲間)[S]"] = stationList["予讃線/愛ある伊予灘線(向井原-伊予大洲間)[S]"] =
addStationPosition( addStationPosition(
concatBetweenStations( concatBetweenStations(
@ -195,15 +211,11 @@ export const getStationList = async () => {
, ,
stationList["日英対応表"] stationList["日英対応表"]
); );
const tokushimaCurrent = addStationPosition( stationList["徳島線(徳島-阿波池田間)[B]"] = addStationPosition(
concatBetweenStations(stationList["徳島線(徳島-阿波池田間)[B]"]), concatBetweenStations(stationList["徳島線(徳島-阿波池田間)[B]"]),
, ,
stationList["日英対応表"] stationList["日英対応表"]
); );
stationList["徳島線(徳島-阿波池田間)[B]"] = [
tokushimaCurrent[tokushimaCurrent.length - 1],
...tokushimaCurrent,
];
stationList["徳島線(徳島-阿波池田間)[B]"].pop(); stationList["徳島線(徳島-阿波池田間)[B]"].pop();
stationList["瀬戸大橋線(児島-宇多津間)[M]"] = [ stationList["瀬戸大橋線(児島-宇多津間)[M]"] = [
{ {

View File

@ -5,7 +5,7 @@ type nameString =
| "SPCL" | "SPCL"
| "Normal" | "Normal"
| string; | string;
type colorString = "aqua" | "red" | "#297bff" | "white"; type colorString = "aqua" | "red" | "#297bff" | "white" | "pink";
type trainTypeString = type trainTypeString =
| "快速" | "快速"
| "特急" | "特急"
@ -28,7 +28,7 @@ export const getTrainType: getTrainType = (nameString) => {
case "LTDEXP": case "LTDEXP":
return { color: "red", name: "特急", data: "express" }; return { color: "red", name: "特急", data: "express" };
case "NightLTDEXP": case "NightLTDEXP":
return { color: "red", name: "寝台特急", data: "express" }; return { color: "pink", name: "寝台特急", data: "express" };
case "SPCL": case "SPCL":
case "SPCL_Rapid": case "SPCL_Rapid":
case "SPCL_EXP": case "SPCL_EXP":

View File

@ -2,13 +2,15 @@ type InjectJavascriptData = (
a: string, a: string,
b: string, b: string,
c: string, c: string,
d: string d: string,
e: string
) => string; ) => string;
export const injectJavascriptData: InjectJavascriptData = ( export const injectJavascriptData: InjectJavascriptData = (
mapSwitch, mapSwitch,
iconSetting, iconSetting,
stationMenu, stationMenu,
trainMenu trainMenu,
uiSetting
) => { ) => {
// 一番上のメニュー非表示 地図スイッチによって切り替え // 一番上のメニュー非表示 地図スイッチによって切り替え
const topMenu = const topMenu =
@ -28,10 +30,29 @@ export const injectJavascriptData: InjectJavascriptData = (
// 上部ヘッダーの取り扱い、自動再読み込み、setStringsの実行 // 上部ヘッダーの取り扱い、自動再読み込み、setStringsの実行
const bootData = ` const bootData = `
let stationList = {};
fetch("https://n8n.haruk.in/webhook/jr-shikoku-station-list").then((response) => response.json()).then((data) => {
stationList = data;
});
let trainDataList = []; let trainDataList = [];
fetch("https://n8n.haruk.in/webhook/jr-shikoku-position-custom-datalist").then((response) => response.json()).then((data) => { fetch("https://n8n.haruk.in/webhook/jr-shikoku-position-custom-datalist").then((response) => response.json()).then((data) => {
trainDataList = data[0].data; trainDataList = data[0].data;
}).then(()=>setReload()); }).then(()=>setReload());
let trainDiagramData2 = {};
fetch("https://n8n.haruk.in/webhook/JR-shikoku-diagram-migrate-original")
.then((response) => response.json())
.then((res)=>res.data)
.then((res) => {
const data = {};
res.forEach((d) => {
const keys = Object.keys(d);
data[keys] = d[keys];
});
return data;
})
.then((data) => {
trainDiagramData2 = data;
});
let probremsData = []; let probremsData = [];
fetch("https://n8n.haruk.in/webhook/jrshikoku-position-problems").then((response) => response.json()).then((data) => { fetch("https://n8n.haruk.in/webhook/jrshikoku-position-problems").then((response) => response.json()).then((data) => {
probremsData = data.data; probremsData = data.data;
@ -52,7 +73,7 @@ export const injectJavascriptData: InjectJavascriptData = (
const trainIcon = ` const trainIcon = `
const setStationIcon = (setIconElem,img,hasProblem) =>{ const setStationIcon = (setIconElem,img,hasProblem) =>{
const position = setIconElem.getAttribute("style").includes("left"); const position = setIconElem.getAttribute("style").includes("left");
setIconElem.insertAdjacentHTML('beforebegin', "<img src="+img+" style='float:"+(position ? 'left' : 'right')+";height:20px;'>"); setIconElem.insertAdjacentHTML('beforebegin', "<img src="+img+" style='float:"+(position ? 'left' : 'right')+";height:22px;margin: 2px;'>");
setIconElem.remove(); setIconElem.remove();
} }
@ -998,52 +1019,6 @@ export const injectJavascriptData: InjectJavascriptData = (
`; `;
const normal_train_name = ` const normal_train_name = `
const nameReplace = (,,,hasProblem) =>{
let isWanman = false;
let trainName = "";
let viaData = "";
let ToData = "";
let TrainNumber = ;
try{
const diagram = trainTimeInfo[];
if(diagram){
const diagramData = diagram.split("#");
ToData = diagramData[diagramData.length - 2].split(",")[0];
}
}catch(e){}
if(.split(":")[1]){
const textBase = .split(":")[1].replace("\\r","");
trainName = textBase;
}
if(.match("サンポート")){
const textBase = .split(":")[1].replace("\\r","");
trainName = textBase;
}
if(new RegExp(/^4[1-9]\\d\\d[DM]$/).test() || new RegExp(/^5[1-7]\\d\\d[DM]$/).test() || new RegExp(/^3[2-9]\\d\\d[DM]$/).test(TrainNumber) ){
flag=true;
isWanman = true;
}
if(new RegExp(/^49[0-4]\\dD$/).test() || new RegExp(/^9[0-4]\\dD$/).test()){
viaData = "(海経由)";
}
if(new RegExp(/^46\\d\\dD$/).test() || new RegExp(/^6\\d\\dD$/).test()){
viaData = "(内子経由)";
}
if(.indexOf("H") != -1){
trainName = "試運転";
}
else if(.indexOf("R") != -1){
trainName = "回送";
}
else if(.indexOf("S") != -1){
trainName = "臨時列車";
}
const getJRF = num =>{ const getJRF = num =>{
switch(num){ switch(num){
case "71": case "71":
@ -1073,8 +1048,66 @@ export const injectJavascriptData: InjectJavascriptData = (
return "松山貨物→高松(タ)"; return "松山貨物→高松(タ)";
case "9070": case "9070":
return "臨時貨物"; return "臨時貨物";
default:
return undefined;
} }
} }
const nameReplace = (,,,hasProblem) =>{
let isWanman = false;
let trainName = "";
let trainType = "";
let trainTypeColor = "black";
let viaData = "";
let ToData = "";
let TrainNumber = ;
try{
const diagram = trainDiagramData2[] || trainTimeInfo[];
if(diagram){
const diagramData = diagram.split("#");
ToData = diagramData[diagramData.length - 2].split(",")[0];
}
}catch(e){}
if(.split(":")[1]){
const textBase = .split(":")[1].replace("\\r","");
trainName = textBase;
}
if(.match("サンポート")){
const textBase = .split(":")[1].replace("\\r","");
trainName = textBase;
}
if(new RegExp(/^4[1-9]\\d\\d[DM]$/).test() || new RegExp(/^5[1-7]\\d\\d[DM]$/).test() || new RegExp(/^3[2-9]\\d\\d[DM]$/).test(TrainNumber) ){
flag=true;
isWanman = true;
}
if(new RegExp(/^49[0-4]\\dD$/).test() || new RegExp(/^9[0-4]\\dD$/).test()){
viaData = "(海経由)";
}
if(new RegExp(/^46\\d\\dD$/).test() || new RegExp(/^6\\d\\dD$/).test()){
viaData = "(内子経由)";
}
if(.indexOf("H") != -1){
trainName = "試運転";
}
else if(.indexOf("R") != -1){
trainName = "回送";
}
else if(.indexOf("E") != -1){
trainName = "回送";
}
else if(.indexOf("A") != -1){
trainName = "回送";
}
else if(.indexOf("B") != -1){
trainName = "回送";
}
else if(.indexOf("S") != -1){
trainName = "臨時列車";
}
let JRF = true; let JRF = true;
const JRFTemp = getJRF(); const JRFTemp = getJRF();
@ -1231,10 +1264,49 @@ export const injectJavascriptData: InjectJavascriptData = (
getThrew(); getThrew();
if(trainDataList.find(e => e.id === ) !== undefined){ if(trainDataList.find(e => e.id === ) !== undefined){
const data = trainDataList.find(e => e.id === ); const data = trainDataList.find(e => e.id === );
//{id,isWanman,trainName,viaData,ToData} //{id,isWanman,trainName,viaData,ToData,TrainNumber,JRF,type,infoUrl,trainNumDistance,info,infogram}
trainType = (()=>{
switch(data.type){
case "Normal":
trainTypeColor = "black";
return "普通";
case "OneMan":
trainTypeColor = "black";
return "普通";
case "Freight":
trainTypeColor = "black";
return "貨物";
case "SPCL_Normal":
trainTypeColor = "#297bff";
return "臨時";
case "SPCL_Rapid":
trainTypeColor = "#297bff";
return "臨時快速";
case "SPCL_LTDEXP":
trainTypeColor = "#297bff";
return "臨時特急";
case "LTDEXP":
trainTypeColor = "red";
return "特急";
case "NightLTDEXP":
trainTypeColor = "rgb(211, 0, 176)";
return "寝台特急";
case "OneManRapid":
trainTypeColor = "rgba(0, 140, 255, 1)";
return "快速";
case "Rapid":
trainTypeColor = "rgba(0, 140, 255, 1)";
return "快速";
default:
return "";
}
})();
isWanman = data.isWanman; isWanman = data.isWanman;
if(data.trainName != ""){ if(data.trainName != ""){
trainName = data.trainName; trainName = data.trainName;
if(data.trainNumDistance != null){
trainName += parseInt(.replace("M", "").replace("D", "")) - data.trainNumDistance+"号";
}
} }
if(data.viaData != ""){ if(data.viaData != ""){
viaData = data.viaData; viaData = data.viaData;
@ -1247,18 +1319,167 @@ export const injectJavascriptData: InjectJavascriptData = (
const returnText1 = (isWanman ? "ワンマン " : "") + trainName + viaData; const returnText1 = (isWanman ? "ワンマン " : "") + trainName + viaData;
const returnText2 = (ToData ? ToData+"行 " : ToData)+ TrainNumber; const returnText2 = (ToData ? ToData+"行 " : ToData)+ TrainNumber;
.innerText = ""; .innerText = "";
${uiSetting === "tokyo" ? `
let stationIDs = [];
let stationLines = [];
Object.keys(stationList).forEach((key) => {
const data = stationList[key].find(e => e.Station_JP === ToData )?.StationNumber;
if(data){
stationIDs.push(data);
stationLines.push(key);
}
});
let getColors = [];
if(stationLines.length === 0){
getColors = ["rgba(97, 96, 96, 0.81)"];
}else{
getColors = stationLines.map(e => GetLineBarColor(e));
}
let yosan2Color = undefined;
if(viaData == "(内子経由)"){
yosan2Color ="#F5AC13";
}
else if(viaData == "(海経由)"){
yosan2Color = "#9AA7D7";
}
const gradient = getColors.length > 1 ? "linear-gradient(130deg, " + getColors[0] + " 0%, "+ getColors[0]+"50%, "+ getColors[1]+"50%, " + getColors[1] + " 100%)" : getColors[0];
.insertAdjacentHTML('beforebegin', "<p style='font-size:6px;padding:0;color:black;text-align:center;'>" + TrainNumber + (JRF ? "":"レ") + "</p>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:8px;font-weight:bold;padding:0;color: black;text-align:center;'>" + (isWanman ? "ワンマン " : "") + "</p>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:6px;font-weight:bold;padding:0;color: black;text-align:center;border-bottom-style:solid;border-bottom-width: "+(!!yosan2Color ? "2px" : "0px")+";border-color:" + yosan2Color + "'>" + viaData + "</p>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:8px;font-weight:bold;padding:0;color: black;text-align:center;'>" + trainName + "</p>");
.insertAdjacentHTML('beforebegin', "<div style='width:100%;background:" + gradient + ";'><p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:white;align-items:center;align-content:center;text-align:center;text-shadow:1px 1px 0px #00000030, -1px -1px 0px #00000030,-1px 1px 0px #00000030, 1px -1px 0px #00000030,1px 0px 0px #00000030, -1px 0px 0px #00000030,0px 1px 0px #00000030, 0px -1px 0px #00000030;'>" + (ToData ? ToData + "行" : ToData) + "</p></div>");
.insertAdjacentHTML('beforebegin', "<div style='width:100%;background:" + trainTypeColor + ";'><p style='font-size:10px;font-weight:bold;font-style:italic;padding:0;color: white;text-align:center;'>" + trainType + "</p></div>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:8px;font-weight:bold;padding:0;text-align:center;color: "+(hasProblem ? "red":"black")+";'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p>");
`: `
.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: black;'>" + returnText1 + "</p>"); .insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: black;'>" + returnText1 + "</p>");
.insertAdjacentHTML('beforebegin', "<div style='display:inline-flex;flex-direction:row;'><p style='font-size:10px;font-weight: bold;padding:0;color:black;'>" + (ToData ? ToData + "行 " : ToData) + "</p><p style='font-size:10px;padding:0;color:black;'>" + TrainNumber + (JRF ? "":"レ") + "</p></div>"); .insertAdjacentHTML('beforebegin', "<div style='display:inline-flex;flex-direction:row;'><p style='font-size:10px;font-weight: bold;padding:0;color:black;'>" + (ToData ? ToData + "行 " : ToData) + "</p><p style='font-size:10px;padding:0;color:black;'>" + TrainNumber + (JRF ? "":"レ") + "</p></div>");
.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: "+(hasProblem ? "red":"black")+";'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p>"); .insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: "+(hasProblem ? "red":"black")+";'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p>");
`}
} }
`; `;
const textInsert = const textInsert =
` `
const setNewTrainItem = (element,hasProblem)=>{
var = element.getAttribute('offclick').split('"')[1];
const JRFTemp = getJRF();
if(element.getAttribute('offclick').includes("express")){
element.style.borderColor = 'rgba(255, 0, 0, 1)';
}else if(element.getAttribute('offclick').includes("rapid")){
element.style.borderColor = 'rgba(0, 140, 255, 1)';
}else if(JRFTemp){
element.style.borderColor = 'rgba(0, 134, 158, 0.8)';
}else{
element.style.borderColor = 'white';
}
element.style.borderWidth = '2px';
element.style.borderStyle = 'solid';
element.style.borderRadius = '10%';
switch(true){
case .indexOf("H") != -1:
case .indexOf("R") != -1:
case .indexOf("E") != -1:
case .indexOf("A") != -1:
case .indexOf("B") != -1:
case !!JRFTemp:
element.style.backgroundColor = 'rgba(199, 199, 199, 0.8)';
break;
default:
element.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
break;
}
if(hasProblem){
element.style.boxShadow = '0 0 10px rgba(255, 0, 0, 0.9)';
}else{
element.style.boxShadow = '0 0 4px rgba(0, 0, 0, 0.2)';
}
element.style.margin = '2px';
element.style.display = 'flex';
element.style.alignItems = 'center';
element.style.justifyContent = 'center';
element.style.width = '4.5em';
element.style.minHeight = '80px';
element.style.height = '100%';
element.getElementsByTagName("img")[0].style.float = 'unset';
element.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
element.style.transition = 'transform 0.1s ease-in-out';
element.addEventListener('touchstart', () => element.style.transform = 'scale(0.8)');
element.addEventListener('touchend', () => element.style.transform = 'scale(1)');
if(element.getAttribute("style").includes("left")){
// borderを使って五角形を生成 下り
element.style.borderRadius = '10% 10% 40% 40%';
element.style.flexDirection = 'column-reverse';
}
else if(element.getAttribute("style").includes("right")){
// borderを使って五角形を生成 上り
element.style.borderRadius = '40% 40% 10% 10%';
element.style.flexDirection = 'column';
}
}
//列番付与 //列番付与
const setStrings = () =>{ const setStrings = () =>{
try { try {
const elements = document.querySelectorAll('#disp > div > div > div[onclick]'); const elements = document.querySelectorAll('#disp > div > div > div[onclick]');
const setNewTrainItemUI = ()=>{
const elementBaseBase = document.querySelectorAll('[id^="stationBlock"]');
elementBaseBase.forEach(e=>{ //それぞれの駅ブロック横一列
e.style.display = 'flex';
e.style.height = "unset";
e.style.flexDirection = 'row';
//e.style.alignItems = "left";
e.style.justifyContent = 'center';
const x = e.querySelectorAll(':scope > div');//配下のdiv要素を選択
//x[0] 登りブロック x[2] 下りブロック x[1] 駅ブロック
const aaa = (x2,pos) => {
x2.style.display = 'flex';
x2.style.flexDirection = 'row';
if(pos == "right"){
x2.style.alignItems = 'flex-start';
x2.style.justifyContent = 'flex-start';
}else if(pos == "left"){
x2.style.alignItems = 'flex-end';
x2.style.justifyContent = 'flex-end';
}
x2.style.flexWrap = 'wrap';
x2.style.width = '100%';
x2.style.height = "100%";
}
const aaa2 = (x2) => {
x2.style.display = 'flex';
x2.style.flexDirection = 'row';
x2.style.alignItems = 'center';
x2.style.justifyContent = 'center';
x2.style.flexWrap = 'wrap';
x2.style.width = '100%';
x2.style.height = "unset";
const x3 = x2.querySelectorAll(":scope > div");
x3.forEach(i=>{
i.style.position = "unset";
i.style.display = "flex";
i.style.flexDirection = "column";
i.style.flex = "1";
i.style.backgroundColor = "#00000000";
i.querySelectorAll(":scope > *").forEach(j=>{
j.style.width = "100%";
j.style.textAlign = "center";
j.style.margin = "5px";
j.style.padding = "5px";
});
});
}
aaa(x[0],"left");
aaa2(x[1]);
aaa(x[2],"right");
});
}
${uiSetting === "tokyo" ? `setNewTrainItemUI();`: ``}
for (let element of elements) { for (let element of elements) {
if(element.getAttribute('offclick')){ continue; } if(element.getAttribute('offclick')){ continue; }
element.setAttribute('offclick',element.getAttribute('onclick')) element.setAttribute('offclick',element.getAttribute('onclick'))
@ -1266,7 +1487,7 @@ const setStrings = () =>{
var = element.getElementsByTagName("p")[0]; var = element.getElementsByTagName("p")[0];
var = element.getAttribute('offclick').split('"')[1]; var = element.getAttribute('offclick').split('"')[1];
var = element.getAttribute('offclick').split('"')[3]; var = element.getAttribute('offclick').split('"')[3];
const trainData = trainPositionDatas.find(e => e.TrainNum == ); const trainData = trainPositionDatas.filter(e=>!(e.Pos && e.Pos.includes("予告窓"))).find(e => e.TrainNum == );
const hasProblem = probremsData.find((e)=>{ const hasProblem = probremsData.find((e)=>{
return e.TrainNum == trainData.TrainNum && e.Pos == trainData.Pos; return e.TrainNum == trainData.TrainNum && e.Pos == trainData.Pos;
}); });
@ -1289,6 +1510,7 @@ const setStrings = () =>{
} }
` : ""} ` : ""}
nameReplace(,,,hasProblem); nameReplace(,,,hasProblem);
${uiSetting === "tokyo" ? `setNewTrainItem(element,hasProblem);`: ``}
} }
try{ try{
for(let d of document.getElementById('disp').childNodes){ for(let d of document.getElementById('disp').childNodes){

682
menu.js
View File

@ -1,71 +1,106 @@
import React, { useRef, useState, useEffect } from "react"; import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import Carousel from "react-native-reanimated-carousel";
import { import {
Platform, Platform,
View, View,
ScrollView, ScrollView,
Linking, useWindowDimensions,
LayoutAnimation,
Text, Text,
TouchableOpacity, TouchableOpacity,
LayoutAnimation,
Dimensions,
} from "react-native"; } from "react-native";
import Constants from "expo-constants"; import Constants from "expo-constants";
import * as Location from "expo-location"; import {
configureReanimatedLogger,
ReanimatedLogLevel,
} from "react-native-reanimated";
import StatusbarDetect from "./StatusbarDetect"; import StatusbarDetect from "./StatusbarDetect";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
import { Ionicons } from "@expo/vector-icons";
import LottieView from "lottie-react-native";
import { parseAllTrainDiagram } from "./lib/parseAllTrainDiagram";
import LED_vision from "./components/発車時刻表/LED_vidion"; import LED_vision from "./components/発車時刻表/LED_vidion";
import Sign from "./components/駅名表/Sign";
import { TitleBar } from "./components/Menu/TitleBar"; import { TitleBar } from "./components/Menu/TitleBar";
import { FixedContentBottom } from "./components/Menu/FixedContentBottom"; import { FixedContentBottom } from "./components/Menu/FixedContentBottom";
import { UsefulBox } from "./components/atom/UsefulBox"; import { lineList, stationIDPair } from "./lib/getStationList";
import { lineList } from "./lib/getStationList";
import useInterval from "./lib/useInterval";
import { HeaderConfig } from "./lib/HeaderConfig";
import { useFavoriteStation } from "./stateBox/useFavoriteStation"; import { useFavoriteStation } from "./stateBox/useFavoriteStation";
import { SheetManager } from "react-native-actions-sheet";
import { useTrainDelayData } from "./stateBox/useTrainDelayData";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { useStationList } from "./stateBox/useStationList"; import { useStationList } from "./stateBox/useStationList";
import { StationNumber } from "./components/Menu/StationPagination"; import { TopMenuButton } from "@/components/Menu/TopMenuButton";
import lineColorList from "./assets/originData/lineColorList"; import { JRSTraInfoBox } from "@/components/Menu/JRSTraInfoBox";
import MapView, { Marker } from "react-native-maps";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import { CarouselBox } from "./components/Menu/Carousel/CarouselBox";
import { CarouselTypeChanger } from "./components/Menu/Carousel/CarouselTypeChanger";
import { useUserPosition } from "./stateBox/useUserPosition";
import { AS } from "./storageControl"; import { AS } from "./storageControl";
import { SimpleDot } from "./components/Menu/SimpleDot"; import { lineList_LineWebID } from "./lib/getStationList";
import { useAllTrainDiagram } from "./stateBox/useAllTrainDiagram"; import { Ionicons } from "@expo/vector-icons";
import { SearchUnitBox } from "./components/Menu/RailScope/SearchUnitBox";
export default function Menu({ getCurrentTrain }) { configureReanimatedLogger({
const { navigate } = useNavigation(); level: ReanimatedLogLevel.error, // Set the log level to error
strict: true, // Reanimated runs in strict mode by default
});
export default function Menu(props) {
const { scrollRef, mapHeight, MapFullHeight, mapMode, setMapMode } = props;
const { navigate, addListener, isFocused } = useNavigation();
const { favoriteStation } = useFavoriteStation(); const { favoriteStation } = useFavoriteStation();
const { originalStationList } = useStationList(); const { originalStationList } = useStationList();
const { height, width } = useWindowDimensions();
//位置情報 const { bottom, left, right, top } = useSafeAreaInsets();
const [locationStatus, setLocationStatus] = useState(null); const tabBarHeight = useBottomTabBarHeight();
useEffect(() => { const [stationListMode, setStationListMode] = useState(
if (Platform.OS == "web") return; /*<"position"|"favorite">*/ "position"
Location.requestForegroundPermissionsAsync().then((data) => {
setLocationStatus(
Platform.OS == "ios"
? data.status == "granted"
: data.android.accuracy == "fine"
); );
useEffect(() => {
AS.getItem("stationListMode")
.then((res) => setStationListMode(res))
.catch((e) => {
// AS.setItem("stationListMode", "position");
}); });
}, []); }, []);
const mapsRef = useRef(null);
const getCurrentPosition = () => { const returnToTop = (bool = true) => {
if (!locationStatus) return () => {}; scrollRef?.current.scrollTo({
Location.getCurrentPositionAsync({}).then((location) => y: mapHeight > 80 ? mapHeight - 80 : 0,
makeCurrentStation(location) animated: bool,
); });
}; };
const goToMap = () => {
scrollRef?.current.scrollTo({
y: 0,
animated: true,
});
};
useEffect(() => {
setTimeout(() => {
returnToTop(false);
}, 10);
}, [mapHeight]);
const [scrollStartPosition, setScrollStartPosition] = useState(0);
const onScrollBeginDrag = (e) => {
LayoutAnimation.configureNext({
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
});
setScrollStartPosition(e.nativeEvent.contentOffset.y);
setMapMode(false);
};
//現在地基準の駅名標リストアップ機能
const { position, locationStatus } = useUserPosition();
useEffect(() => {
if (!position) return () => {};
makeCurrentStation(position);
}, [position, stationListMode]);
const makeCurrentStation = (location) => { const makeCurrentStation = (location) => {
if (!originalStationList) return () => {}; if (!originalStationList) return () => {};
const findStationEachLine = (selectLine) => { const findStationEachLine = (selectLine) => {
const searchArea = 0.002; const searchArea = 0.055; //検索範囲
const _calcDistance = (from, to) => { const _calcDistance = (from, to) => {
let lat = Math.abs(from.lat - to.lat); let lat = Math.abs(from.lat - to.lat);
let lng = Math.abs(from.lng - to.lng); let lng = Math.abs(from.lng - to.lng);
@ -78,94 +113,135 @@ export default function Menu({ getCurrentTrain }) {
lng: location.coords.longitude, lng: location.coords.longitude,
}) < searchArea }) < searchArea
); );
//NearStationを距離の近い順にソート
NearStation.sort((a, b) => {
return (
_calcDistance(a, {
lat: location.coords.latitude,
lng: location.coords.longitude,
}) -
_calcDistance(b, {
lat: location.coords.latitude,
lng: location.coords.longitude,
})
);
});
return NearStation; return NearStation;
}; };
let returnDataBase = lineList let _stList = lineList
.map((d) => findStationEachLine(originalStationList[d])) .map((d) => findStationEachLine(originalStationList[d]))
.filter((d) => d.length > 0) .filter((d) => d.length > 0)
.reduce((pre, current) => { .reduce((pre, current) => {
pre.push(...current); pre.push(...current);
return pre; return pre;
}, []); }, []);
if (returnDataBase.length) { if (_stList.length == 0) setNearPositionStation([]);
let currentStation = currentStation == undefined ? [] : currentStation; else {
if (currentStation.toString() != returnDataBase.toString()) { let returnData = [];
setCurrentStation(returnDataBase); _stList.forEach((d, index, array) => {
} const stationName = d.Station_JP;
} else { if (returnData.findIndex((d) => d[0].Station_JP == stationName) != -1)
setCurrentStation(undefined); return;
returnData.push(array.filter((d2) => d2.Station_JP == stationName));
});
//returnDataを距離の近い順にソート
returnData.sort((a, b) => {
const _calcDistance = (from, to) => {
let lat = Math.abs(from.lat - to.lat);
let lng = Math.abs(from.lng - to.lng);
return Math.sqrt(lat * lat + lng * lng);
};
return (
_calcDistance(a[0], {
lat: location.coords.latitude,
lng: location.coords.longitude,
}) -
_calcDistance(b[0], {
lat: location.coords.latitude,
lng: location.coords.longitude,
})
);
});
setNearPositionStation(returnData);
} }
}; };
useEffect(getCurrentPosition, [locationStatus]); const [nearPositionStation, setNearPositionStation] = useState([]); //第三要素
useInterval(getCurrentPosition, 5000);
const [currentStation, setCurrentStation] = useState(undefined); //第三要素 const [listIndex, setListIndex] = useState(0);
const carouselRef = useRef(); const [listUpStation, setListUpStation] = useState([]);
const [selectedCurrentStation, setSelectedCurrentStation] = useState(0); const [isSearchMode, setisSearchMode] = useState(false);
useLayoutEffect(() => {
const [allStationData, setAllStationData] = useState([]); if (!!isSearchMode) {
const returnData = [];
Object.keys(lineList_LineWebID).forEach((d, indexBase) => {
originalStationList[d].forEach((D, index) => {
if (
isSearchMode &&
isSearchMode != stationIDPair[lineList_LineWebID[d]]
)
return;
const latlng = [D.lat, D.lng];
if (latlng.length == 0) return null;
if (D.StationNumber == undefined) {
return null;
}
returnData.push([D]);
});
});
if (JSON.stringify(returnData) == JSON.stringify(listUpStation)) return;
setListUpStation(returnData);
} else if (stationListMode == "position") {
const returnData = nearPositionStation.filter((d) => d != undefined);
if (JSON.stringify(returnData) == JSON.stringify(listUpStation)) return;
setListUpStation(returnData);
} else {
const returnData = favoriteStation.filter((d) => d != undefined);
if (JSON.stringify(returnData) == JSON.stringify(listUpStation)) return;
setListUpStation(returnData);
}
}, [nearPositionStation, favoriteStation, stationListMode, isSearchMode]);
useEffect(() => { useEffect(() => {
setAllStationData( if (listUpStation.length == 0) {
[currentStation, ...favoriteStation].filter((d) => d != undefined) setListIndex(0);
);
}, [currentStation, favoriteStation]);
useEffect(() => {
if (allStationData.length == 0) {
setSelectedCurrentStation(0);
return; return;
} }
if (allStationData[selectedCurrentStation] == undefined) { if (listUpStation.length == 1) {
const count = selectedCurrentStation - 1; setListIndex(0);
setSelectedCurrentStation(count); return;
} }
}, [selectedCurrentStation, currentStation, allStationData]); if (listUpStation[listIndex] == undefined) {
const count = listIndex - 1;
setMapMode(false);
setListIndex(count);
}
}, [listIndex, listUpStation, isSearchMode]);
useEffect(() => { useEffect(() => {
if (!carouselRef.current) return; if (originalStationList == undefined) return;
carouselRef?.current.scrollTo({ if (listUpStation.length == 0) return;
count: selectedCurrentStation - carouselRef.current.getCurrentIndex(), if (listUpStation[listIndex] == undefined) return;
animated: true, const { lat, lng } = listUpStation[listIndex][0];
}); const mapRegion = {
}, [selectedCurrentStation]); latitude: lat,
longitude: lng,
//全列車ダイヤリストを作成するuseEffect latitudeDelta: 0.05,
const { allTrainDiagram:trainDiagram} = useAllTrainDiagram(); longitudeDelta: 0.05,
const oPSign = () => {
const payload = {
currentStation:
originalStationList &&
allStationData.length != 0 &&
allStationData[selectedCurrentStation],
navigate: navigate,
goTo: "menu",
useShow: () => SheetManager.show("StationDetailView", { payload }),
onExit: () => SheetManager.hide("StationDetailView"),
}; };
SheetManager.show("StationDetailView", { payload }); if (mapMode) {
}; mapsRef?.current.fitToCoordinates(
listUpStation.map((d) => ({
const [dotButton, setDotButton] = useState(false); latitude: parseFloat(d[0].lat),
longitude: parseFloat(d[0].lng),
useEffect(() => { })),
AS.getItem("CarouselSettings/activeDotSettings").then((data) => { { edgePadding: { top: 80, bottom: 120, left: 50, right: 50 } } // Add margin values here
setDotButton(data === "true");
});
}, []);
const oLPSign = () => {
LayoutAnimation.configureNext({
duration: 600,
update: { type: "spring", springDamping: 0.5 },
});
AS.setItem(
"CarouselSettings/activeDotSettings",
!dotButton ? "true" : "false"
); );
setDotButton(!dotButton); } else {
}; mapsRef.current.animateToRegion(mapRegion, 1000);
const width = Dimensions.get("window").width; }
}, [listIndex, listUpStation]);
return ( return (
<View <View
style={{ style={{
@ -175,247 +251,175 @@ export default function Menu({ getCurrentTrain }) {
}} }}
> >
<StatusbarDetect /> <StatusbarDetect />
{!mapMode ? (
<TitleBar /> <TitleBar />
<ScrollView> ) : (
<TopMenuButton /> <></>
{originalStationList.length != 0 && allStationData.length != 0 && ( // <Text
<View style={{ flex: 1, paddingTop: 10 }}> // style={{
<Carousel // fontSize: 30,
ref={carouselRef} // color: "#0099CC",
data={originalStationList && allStationData} // fontWeight: "bold",
height={(wp("80%") / 20) * 9 + 10} // position: "absolute",
pagingEnabled={true} // top: 0,
snapEnabled={true} // zIndex: 1000,
loop={false} // fontStyle: "italic",
width={width} // }}
style={{ width: width, alignContent: "center" }} // >
mode="parallax" // JRShikoku RailScope
modeConfig={{ // </Text>
parallaxScrollingScale: 1,
parallaxScrollingOffset: 100,
parallaxAdjacentItemScale: 0.8,
}}
onSnapToItem={setSelectedCurrentStation}
renderItem={({ item, index }) => {
return (
<View
style={{
backgroundColor: "#0000",
width: width,
flexDirection: "row",
marginLeft: 0,
marginRight: 0,
}}
key={item[0].StationNumber}
>
<View style={{ flex: 1 }} />
<Sign
currentStation={item}
isCurrentStation={item == currentStation}
oP={oPSign}
oLP={oLPSign}
/>
<View style={{ flex: 1 }} />
</View>
);
}}
/>
<View
style={{
flexDirection: "row",
justifyContent: "center",
alignContent: "center",
alignItems: "center",
}}
>
{originalStationList &&
allStationData.map((d, index) => {
const active = index == selectedCurrentStation;
const numberIndex = d[0].StationNumber;
if (dotButton) {
return (
<StationNumber
onPress={() => setSelectedCurrentStation(index)}
currentStation={d}
active={active}
index={numberIndex}
/>
);
} else {
return (
<SimpleDot
onPress={() => setSelectedCurrentStation(index)}
active={active}
index={numberIndex}
/>
);
}
})}
</View>
</View>
)} )}
{allStationData.length != 0 && <ScrollView
originalStationList.length != 0 && ref={scrollRef}
allStationData[selectedCurrentStation] && ( snapToStart={false}
<LED_vision snapToEnd={false}
station={ decelerationRate={"normal"}
originalStationList && allStationData[selectedCurrentStation] snapToOffsets={[mapHeight - 80]}
contentContainerStyle={{
position: "relative",
}}
onScrollBeginDrag={onScrollBeginDrag}
onScrollEndDrag={(e) => {
if (e.nativeEvent.contentOffset.y < mapHeight - 80) {
if (scrollStartPosition > e.nativeEvent.contentOffset.y) {
goToMap();
} else {
returnToTop();
} }
trainDiagram={trainDiagram} }
getCurrentTrain={getCurrentTrain} }}
>
<MapView
ref={mapsRef}
style={{ width: "100%", height: mapMode ? MapFullHeight : mapHeight }}
showsUserLocation={true}
loadingEnabled={true}
showsMyLocationButton={false}
moveOnMarkerPress={false}
showsCompass={false}
//provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 33.774519,
longitude: 133.533306,
latitudeDelta: 1.8, //小さくなるほどズーム
longitudeDelta: 1.8,
}}
onTouchStart={() => {
LayoutAnimation.configureNext({
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
});
setMapMode(true);
goToMap();
}}
>
{listUpStation.map(([{ lat, lng, StationNumber }], index) => (
<Marker
key={index + StationNumber}
coordinate={{
latitude: parseFloat(lat),
longitude: parseFloat(lng),
}}
image={require("@/assets/reccha-small.png")}
onPress={() => {
setMapMode(false);
setListIndex(index);
if (mapsRef.current) {
mapsRef.current.animateToRegion(
{
latitude: parseFloat(lat),
longitude: parseFloat(lng),
latitudeDelta: 0.05,
longitudeDelta: 0.05,
},
1000
);
}
LayoutAnimation.configureNext({
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
});
returnToTop();
}}
/>
))}
</MapView>
{!mapMode && (
<CarouselTypeChanger
{...{
locationStatus,
position,
mapsRef,
scrollRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation: setListIndex,
mapMode,
setMapMode,
isSearchMode,
setisSearchMode,
}}
/>
)}
{originalStationList.length != 0 && (
<>
<CarouselBox
{...{
originalStationList,
listUpStation,
nearPositionStation,
setListIndex,
listIndex,
navigate,
stationListMode,
isSearchMode
}}
/>
{listUpStation[listIndex] && (
<LED_vision
station={listUpStation[listIndex]}
navigate={navigate} navigate={navigate}
openStationACFromEachTrainInfo={() => {}} openStationACFromEachTrainInfo={() => {}}
/> />
)} )}
</>
)}
<TopMenuButton />
<JRSTraInfoBox /> <JRSTraInfoBox />
<FixedContentBottom navigate={navigate} /> <FixedContentBottom navigate={navigate} />
</ScrollView> </ScrollView>
{mapMode && (
<CarouselTypeChanger
{...{
locationStatus,
position,
mapsRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation: setListIndex,
mapMode,
setMapMode,
isSearchMode,
setisSearchMode,
}}
/>
)}
</View> </View>
); );
} }
const TopMenuButton = () => {
const buttonList = [
{
backgroundColor: "#F89038",
icon: "train-car",
onPress: () =>
Linking.openURL("https://www.jr-shikoku.co.jp/01_trainbus/sp/"),
title: "駅・鉄道情報",
},
{
backgroundColor: "#EA4752",
icon: "google-spreadsheet",
onPress: () =>
Linking.openURL(
"https://www.jr-shikoku.co.jp/01_trainbus/jikoku/sp/#mainprice-box"
),
title: "運賃表",
},
{
backgroundColor: "#91C31F",
icon: "clipboard-list-outline",
onPress: () => Linking.openURL("https://www.jr-shikoku.co.jp/e5489/"),
title: "予約",
},
];
return (
<View style={{ flexDirection: "row" }}>
{buttonList.map((d, index) => (
<UsefulBox
backgroundColor={d.backgroundColor}
icon={d.icon}
flex={1}
onPressButton={d.onPress}
key={index + d.icon}
>
{d.title}
</UsefulBox>
))}
</View>
);
};
const JRSTraInfoBox = () => {
const { getTime, delayData, loadingDelayData, setLoadingDelayData } =
useTrainDelayData();
const styles = {
touch: {
backgroundColor: "#0099CC",
borderRadius: 5,
margin: 10,
borderColor: "black",
borderWidth: 2,
overflow: "hidden",
},
scroll: {
backgroundColor: "#0099CC",
borderRadius: 5,
maxHeight: 300,
},
bottom: {
position: "absolute",
top: 250,
alignItems: "center",
width: "100%",
height: 50,
backgroundColor: "#007FCC88",
},
box: {
padding: 10,
backgroundColor: "white",
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
},
};
return (
<TouchableOpacity
onPress={() => SheetManager.show("JRSTraInfo")}
style={styles.touch}
>
<ScrollView scrollEnabled={false} style={styles.scroll}>
<View
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
>
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
列車遅延速報EX
</Text>
<View style={{ flex: 1 }} />
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
{getTime
? getTime.toLocaleTimeString("ja-JP").split(":")[0] +
":" +
getTime.toLocaleTimeString("ja-JP").split(":")[1]
: NaN}
</Text>
<Ionicons
name="reload"
color="white"
size={30}
style={{ margin: 5 }}
onPress={() => {
setLoadingDelayData(true);
}}
/>
</View>
<View style={styles.box}>
{loadingDelayData ? (
<View style={{ alignItems: "center" }}>
<LottieView
autoPlay
loop
style={{ width: 150, height: 150, backgroundColor: "#fff" }}
source={require("./assets/51690-loading-diamonds.json")}
/>
</View>
) : delayData ? (
delayData.map((d, index, array) => {
let data = d.split(" ");
return (
<View
style={{ flexDirection: "row" }}
key={data[1] + "key" + index}
>
<Text style={{ flex: 15, fontSize: 18 }}>
{data[0].replace("\n", "")}
</Text>
<Text style={{ flex: 5, fontSize: 18 }}>{data[1]}</Text>
<Text style={{ flex: 6, fontSize: 18 }}>{data[3]}</Text>
</View>
);
})
) : (
<Text>現在5分以上の遅れはありません</Text>
)}
</View>
</ScrollView>
<View style={styles.bottom}>
<View style={{ flex: 1 }} />
<Text style={{ color: "white", fontWeight: "bold", fontSize: 20 }}>
詳細を見る
</Text>
<View style={{ flex: 1 }} />
</View>
</TouchableOpacity>
);
};

View File

@ -4,6 +4,7 @@ import React, { createContext, useContext, useEffect, useState } from "react";
const initialState = { const initialState = {
allTrainDiagram: undefined, allTrainDiagram: undefined,
setAllTrainDiagram: () => {}, setAllTrainDiagram: () => {},
allCustonTrainData: [],
}; };
const AllTrainDiagramContext = createContext(initialState); const AllTrainDiagramContext = createContext(initialState);
@ -12,6 +13,7 @@ export const useAllTrainDiagram = () => useContext(AllTrainDiagramContext);
export const AllTrainDiagramProvider = ({ children }) => { export const AllTrainDiagramProvider = ({ children }) => {
const [allTrainDiagram, setAllTrainDiagram] = useState(trainList); const [allTrainDiagram, setAllTrainDiagram] = useState(trainList);
const [allCustonTrainData, setAllCustonTrainData] = useState([]); // カスタム列車データ
const [keyList, setKeyList] = useState(); // 第二要素 const [keyList, setKeyList] = useState(); // 第二要素
useEffect( useEffect(
() => allTrainDiagram && setKeyList(Object.keys(allTrainDiagram)), () => allTrainDiagram && setKeyList(Object.keys(allTrainDiagram)),
@ -41,10 +43,26 @@ export const AllTrainDiagramProvider = ({ children }) => {
}); });
}); });
}, []); }, []);
useEffect(() => {
// カスタム列車データの取得
fetch("https://n8n.haruk.in/webhook/jr-shikoku-position-custom-datalist")
.then((res) => res.json())
.then((res) => {
setAllCustonTrainData(res[0].data);
})
.catch(() => {
alert("カスタム列車データの取得に失敗しました。");
});
}, []);
return ( return (
<AllTrainDiagramContext.Provider <AllTrainDiagramContext.Provider
value={{ allTrainDiagram, setAllTrainDiagram, keyList }} value={{
allTrainDiagram,
setAllTrainDiagram,
allCustonTrainData,
keyList,
}}
> >
{children} {children}
</AllTrainDiagramContext.Provider> </AllTrainDiagramContext.Provider>

View File

@ -15,6 +15,7 @@ const initialState = {
currentTrainLoading: "loading", currentTrainLoading: "loading",
setCurrentTrainLoading: () => {}, setCurrentTrainLoading: () => {},
getCurrentTrain: () => {}, getCurrentTrain: () => {},
inject: (i) => {},
}; };
const CurrentTrainContext = createContext(initialState); const CurrentTrainContext = createContext(initialState);

View File

@ -29,10 +29,10 @@ type Props = {
}; };
export const FavoriteStationProvider:FC<Props> = ({ children }) => { export const FavoriteStationProvider:FC<Props> = ({ children }) => {
const [favoriteStation, setFavoriteStation] = useState([]); const [favoriteStation, setFavoriteStation] = useState([]);
const { getStationData } = useStationList(); const { getStationDataFromName } = useStationList();
const lodAddMigration = () => { const lodAddMigration = () => {
const migration = favoriteStation.map((d) => { const migration = favoriteStation.map((d) => {
return getStationData(d[0].Station_JP); return getStationDataFromName(d[0].Station_JP);
}); });
setFavoriteStation(migration); setFavoriteStation(migration);
}; };

View File

@ -5,19 +5,27 @@ import React, {
useEffect, useEffect,
FC, FC,
} from "react"; } from "react";
import { lineList, getStationList } from "../lib/getStationList"; import {
lineList,
getStationList,
lineList_LineWebID,
} from "../lib/getStationList";
type initialStateType = { type initialStateType = {
originalStationList: any[][]; originalStationList: any[][];
setOriginalStationList: React.Dispatch<React.SetStateAction<any[]>>; setOriginalStationList: React.Dispatch<React.SetStateAction<any[]>>;
getStationData: (id: string) => void; getStationDataFromName: (id: string) => any[];
getStationDataFromId: (id: string) => any[];
stationList: any[]; stationList: any[];
getInjectJavascriptAddress: (StationNumber: string) => string;
}; };
const initialState = { const initialState = {
originalStationList: [[]], originalStationList: [[]],
setOriginalStationList: () => {}, setOriginalStationList: () => {},
getStationData: () => {}, getStationDataFromName: () => [],
getStationDataFromId: () => [],
stationList: [], stationList: [],
getInjectJavascriptAddress: (StationNumber: string) => "",
}; };
const StationListContext = createContext<initialStateType>(initialState); const StationListContext = createContext<initialStateType>(initialState);
@ -33,7 +41,21 @@ export const StationListProvider: FC<Props> = ({ children }) => {
useEffect(() => { useEffect(() => {
getStationList().then(setOriginalStationList); getStationList().then(setOriginalStationList);
}, []); }, []);
const getStationData: (name: string) => void = (name) => { const getStationDataFromId: (id: string) => any[] = (id) => {
let returnArray = [];
Object.keys(originalStationList).forEach((key) => {
originalStationList[key].forEach((station) => {
if (station.StationNumber === id) {
returnArray = [
...returnArray,
...getStationDataFromName(station.Station_JP),
];
}
});
});
return returnArray;
};
const getStationDataFromName: (name: string) => any[] = (name) => {
const returnArray = []; const returnArray = [];
Object.keys(originalStationList).forEach((key) => { Object.keys(originalStationList).forEach((key) => {
originalStationList[key].forEach((station) => { originalStationList[key].forEach((station) => {
@ -45,22 +67,55 @@ export const StationListProvider: FC<Props> = ({ children }) => {
return returnArray; return returnArray;
}; };
const [stationList, setStationList] = useState<any[][]>([[]]); const [stationList, setStationList] = useState<any[][]>([[]]);
useEffect(()=>{ useEffect(() => {
if(originalStationList.length === 0) return; if (originalStationList.length === 0) return;
const stationList = const stationList = lineList.map((d) =>
originalStationList &&
lineList.map((d) =>
originalStationList[d].map((a) => ({ originalStationList[d].map((a) => ({
StationNumber: a.StationNumber, StationNumber: a.StationNumber,
StationName: a.Station_JP, StationName: a.Station_JP,
})) }))
); );
setStationList(stationList) setStationList(stationList);
},[originalStationList]) }, [originalStationList]);
const getInjectJavascriptAddress = (StationNumber: string) => {
const bootStationList = [];
Object.keys(originalStationList).forEach((d) => {
let findNearStations = false;
originalStationList[d].forEach((x) => {
let lineName = lineList_LineWebID[d];
if (findNearStations) {
if (x.MyStation) {
bootStationList.push({ line: lineName, station: x });
findNearStations = false;
}
return;
}
if (x.StationNumber == StationNumber) {
if (!x.MyStation) findNearStations = true;
else bootStationList.push({ line: lineName, station: x });
}
});
if (StationNumber == "M12") {
bootStationList.push({
line: "seto",
station: { Station_JP: "児島", MyStation: "0" },
});
}
});
return `MoveDisplayStation('${bootStationList[0].line}_${bootStationList[0].station.MyStation}_${bootStationList[0].station.Station_JP}');document.getElementById("disp").insertAdjacentHTML("afterbegin", "<div />");`;
};
return ( return (
<StationListContext.Provider <StationListContext.Provider
value={{ originalStationList, setOriginalStationList, getStationData, stationList }} value={{
originalStationList,
setOriginalStationList,
getStationDataFromName,
getStationDataFromId,
stationList,
getInjectJavascriptAddress,
}}
> >
{children} {children}
</StationListContext.Provider> </StationListContext.Provider>

68
stateBox/useTopMenu.tsx Normal file
View File

@ -0,0 +1,68 @@
import React, {
createContext,
useContext,
useState,
useEffect,
FC,
} from "react";
import { lineList, getStationList } from "../lib/getStationList";
type initialStateType = {
originalStationList: any[][];
setOriginalStationList: React.Dispatch<React.SetStateAction<any[]>>;
getStationData: (id: string) => void;
stationList: any[];
};
const initialState = {
originalStationList: [[]],
setOriginalStationList: () => {},
getStationData: () => {},
stationList: [],
};
const TopMenuContext = createContext<initialStateType>(initialState);
type Props = {
children: React.ReactNode;
};
export const useTopMenu = () => {
return useContext(TopMenuContext);
};
export const TopMenuProvider: FC<Props> = ({ children }) => {
const [originalStationList, setOriginalStationList] = useState<any[]>([]);
useEffect(() => {
getStationList().then(setOriginalStationList);
}, []);
const getStationData: (name: string) => void = (name) => {
const returnArray = [];
Object.keys(originalStationList).forEach((key) => {
originalStationList[key].forEach((station) => {
if (station.Station_JP === name) {
if (!!station.jslodApi) returnArray.push(station);
}
});
});
return returnArray;
};
const [stationList, setStationList] = useState<any[][]>([[]]);
useEffect(()=>{
if(originalStationList.length === 0) return;
const stationList =
originalStationList &&
lineList.map((d) =>
originalStationList[d].map((a) => ({
StationNumber: a.StationNumber,
StationName: a.Station_JP,
}))
);
setStationList(stationList)
},[originalStationList])
return (
<TopMenuContext.Provider
value={{ originalStationList, setOriginalStationList, getStationData, stationList }}
>
{children}
</TopMenuContext.Provider>
);
};

View File

@ -3,7 +3,7 @@ const initialState = {
getTime: new Date(), getTime: new Date(),
setGetTime: () => {}, setGetTime: () => {},
loadingDelayData: true, loadingDelayData: true,
setLoadingDelayData: () => {}, setLoadingDelayData: (loading) => {},
delayData: undefined, delayData: undefined,
setDelayData: () => {}, setDelayData: () => {},
}; };

View File

@ -5,6 +5,8 @@ import { ASCore } from "../storageControl";
import { getStationList2 } from "../lib/getStationList"; import { getStationList2 } from "../lib/getStationList";
import { injectJavascriptData } from "../lib/webViewInjectjavascript"; import { injectJavascriptData } from "../lib/webViewInjectjavascript";
import { useNotification } from "../stateBox/useNotifications";
const initialState = { const initialState = {
selectedLine: undefined, selectedLine: undefined,
setSelectedLine: () => {}, setSelectedLine: () => {},
@ -16,6 +18,8 @@ const initialState = {
setMapSwitch: () => {}, setMapSwitch: () => {},
stationMenu: undefined, stationMenu: undefined,
setStationMenu: () => {}, setStationMenu: () => {},
uiSetting: undefined,
setUiSetting: () => {},
LoadError: false, LoadError: false,
setLoadError: () => {}, setLoadError: () => {},
trainInfo: { trainInfo: {
@ -26,6 +30,8 @@ const initialState = {
setTrainInfo: () => {}, setTrainInfo: () => {},
trainMenu: "true", trainMenu: "true",
setTrainMenu: () => {}, setTrainMenu: () => {},
updatePermission : false,
setUpdatePermission : () => {},
injectJavascript: "", injectJavascript: "",
}; };
@ -36,6 +42,7 @@ export const useTrainMenu = () => {
}; };
export const TrainMenuProvider = ({ children }) => { export const TrainMenuProvider = ({ children }) => {
const { expoPushToken } = useNotification();
const [selectedLine, setSelectedLine] = useState(undefined); const [selectedLine, setSelectedLine] = useState(undefined);
const [mapsStationData, setMapsStationData] = useState(undefined); const [mapsStationData, setMapsStationData] = useState(undefined);
useEffect(() => { useEffect(() => {
@ -48,6 +55,19 @@ export const TrainMenuProvider = ({ children }) => {
const [stationMenu, setStationMenu] = useState(undefined); const [stationMenu, setStationMenu] = useState(undefined);
const [LoadError, setLoadError] = useState(false); const [LoadError, setLoadError] = useState(false);
//更新権限所有確認
const [updatePermission, setUpdatePermission] = useState(false);
useEffect(()=>{
fetch("https://n8n.haruk.in/webhook/data-edit-permission?token=" + expoPushToken).then((res)=>res.json())
.then((res)=>{
if(res.data == true){
setUpdatePermission(true);
}else{
setUpdatePermission(false);
}
})
}, [expoPushToken])
//列車情報表示関連 //列車情報表示関連
const [trainInfo, setTrainInfo] = useState({ const [trainInfo, setTrainInfo] = useState({
trainNum: undefined, trainNum: undefined,
@ -58,12 +78,16 @@ export const TrainMenuProvider = ({ children }) => {
//駅情報画面用 //駅情報画面用
const [trainMenu, setTrainMenu] = useState("true"); const [trainMenu, setTrainMenu] = useState("true");
//GUIデザインベース
const [uiSetting, setUiSetting] = useState("tokyo");
//地図表示テキスト //地図表示テキスト
const injectJavascript = injectJavascriptData( const injectJavascript = injectJavascriptData(
mapSwitch, mapSwitch,
iconSetting, iconSetting,
stationMenu, stationMenu,
trainMenu trainMenu,
uiSetting
); );
useEffect(() => { useEffect(() => {
@ -75,6 +99,8 @@ export const TrainMenuProvider = ({ children }) => {
ASCore({ k: "stationSwitch", s: setStationMenu, d: "true", u: true }); ASCore({ k: "stationSwitch", s: setStationMenu, d: "true", u: true });
//列車メニュースイッチ //列車メニュースイッチ
ASCore({ k: "trainSwitch", s: setTrainMenu, d: "true", u: true }); ASCore({ k: "trainSwitch", s: setTrainMenu, d: "true", u: true });
//GUIデザインベーススイッチ
ASCore({ k: "uiSetting", s: setUiSetting, d: "tokyo", u: true });
}, []); }, []);
return ( return (
@ -90,12 +116,16 @@ export const TrainMenuProvider = ({ children }) => {
setMapSwitch, setMapSwitch,
stationMenu, stationMenu,
setStationMenu, setStationMenu,
uiSetting,
setUiSetting,
LoadError, LoadError,
setLoadError, setLoadError,
trainInfo, trainInfo,
setTrainInfo, setTrainInfo,
trainMenu, trainMenu,
setTrainMenu, setTrainMenu,
updatePermission,
setUpdatePermission,
injectJavascript, injectJavascript,
}} }}
> >

View File

@ -0,0 +1,76 @@
import React, {
createContext,
useContext,
useState,
useEffect,
FC,
} from "react";
import {
LocationObject,
requestForegroundPermissionsAsync,
getCurrentPositionAsync,
} from "expo-location";
import { Platform } from "react-native";
import useInterval from "@/lib/useInterval";
type initialStateType = {
position: LocationObject | undefined;
getCurrentPosition: () => void;
locationStatus: boolean | null;
getLocationPermission: () => void;
};
const initialState = {
position: undefined,
getCurrentPosition: () => {},
locationStatus: null,
getLocationPermission: () => {},
};
const UserPositionContext = createContext<initialStateType>(initialState);
type Props = {
children: React.ReactNode;
};
export const useUserPosition = () => {
return useContext(UserPositionContext);
};
export const UserPositionProvider: FC<Props> = ({ children }) => {
//位置情報
const [locationStatus, setLocationStatus] = useState<boolean | null>(null);
const [position, setPosition] = useState<LocationObject | undefined>(
undefined
);
const getLocationPermission = async () => {
return requestForegroundPermissionsAsync().then((data) => {
setLocationStatus(
Platform.OS == "ios"
? data.status == "granted"
: data.android.accuracy == "fine"
);
});
};
const getCurrentPosition = () => {
if (!locationStatus) return () => {};
getCurrentPositionAsync({}).then((location) => setPosition(location));
};
useEffect(() => {
if (Platform.OS == "web") return;
getLocationPermission();
}, []);
useEffect(getCurrentPosition, [locationStatus]);
useInterval(getCurrentPosition, 5000);
return (
<UserPositionContext.Provider
value={{
position,
getCurrentPosition,
locationStatus,
getLocationPermission,
}}
>
{children}
</UserPositionContext.Provider>
);
};