Merge commit 'af30d1cbb0ffac53805f28107b3564535219e541' into develop

This commit is contained in:
harukin-expo-dev-env 2025-06-10 14:27:26 +00:00
commit 7395c7e8f4
21 changed files with 1268 additions and 650 deletions

2
App.js
View File

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

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 { useWindowDimensions, Platform } from "react-native";
import Constants from "expo-constants";
import { Dimensions, StatusBar } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { AS } from "./storageControl";
import TrainBase from "./components/trainbaseview";
@ -10,14 +15,15 @@ import Setting from "./components/Settings/settings";
import { useFavoriteStation } from "./stateBox/useFavoriteStation";
import { optionData } from "./lib/stackOption";
import AllTrainDiagramView from "./components/AllTrainDiagramView";
import { useCurrentTrain } from "./stateBox/useCurrentTrain";
import { useNavigation } from "@react-navigation/native";
import { news } from "./config/newsUpdate";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
const Stack = createStackNavigator();
export function MenuPage() {
const { favoriteStation, setFavoriteStation } = useFavoriteStation();
const { getCurrentTrain } = useCurrentTrain();
const { height, width } = useWindowDimensions();
const tabBarHeight = useBottomTabBarHeight();
const navigation = useNavigation();
const { addListener } = navigation;
useEffect(() => {
@ -42,8 +48,34 @@ export function MenuPage() {
})
.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(() => {
const unsubscribe = addListener("tabPress", (e) => {
scrollRef.current.scrollTo({
y: mapHeight - 80,
animated: true,
});
setMapMode(false);
AS.getItem("favoriteStation")
.then((d) => {
const returnData = JSON.parse(d);
@ -55,7 +87,7 @@ export function MenuPage() {
});
return unsubscribe;
}, [navigation]);
}, [navigation, mapHeight]);
return (
<Stack.Navigator>
<Stack.Screen
@ -65,7 +97,15 @@ export function MenuPage() {
gestureEnabled: 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

View File

@ -2,19 +2,14 @@ import React, { useState, useEffect } from "react";
import {
View,
Linking,
Text,
TouchableOpacity,
BackHandler,
Platform,
useWindowDimensions,
} 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 ActionSheet, { SheetManager } from "react-native-actions-sheet";
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 { useBusAndTrainData } from "../../stateBox/useBusAndTrainData";
import { AS } from "../../storageControl";
@ -27,13 +22,14 @@ import { StationTimeTableButton } from "./StationDeteilView/StationTimeTableButt
export const StationDeteilView = (props) => {
if (!props.payload) return <></>;
const { currentStation, navigate, onExit, goTo, useShow } = props.payload;
const { width } = useWindowDimensions();
const { busAndTrainData } = useBusAndTrainData();
const [trainBus, setTrainBus] = useState();
useEffect(() => {
if (!currentStation) return () => {};
const data = busAndTrainData.filter((d) =>
d.name === currentStation[0].Station_JP
const data = busAndTrainData.filter(
(d) => d.name === currentStation[0].Station_JP
);
if (data.length == 0) {
setTrainBus();
@ -89,73 +85,74 @@ export const StationDeteilView = (props) => {
</View>
<View>
{currentStation && (
<View style={{ margin: 10, marginHorizontal: wp("10%") }}>
<Sign
currentStation={currentStation}
oP={() => {
usePDFView == "true"
? Linking.openURL(currentStation[0].StationTimeTable)
: navigate("howto", {
info,
<>
<View style={{ margin: 10, marginHorizontal: width * 0.1 }}>
<Sign
stationID={currentStation[0].StationNumber}
oP={() => {
usePDFView == "true"
? Linking.openURL(currentStation[0].StationTimeTable)
: navigate("howto", {
info,
goTo,
useShow,
});
onExit();
}}
oLP={() =>
Linking.openURL(currentStation[0].StationTimeTable)
}
/>
</View>
{currentStation[0].JrHpUrl &&
currentStation[0].StationNumber != "M12" && (
<駅構内図 //児島例外/
navigate={navigate}
goTo={goTo}
useShow={useShow}
address={currentStation[0].JrHpUrl}
onExit={onExit}
/>
)}
<View style={{ flexDirection: "row" }}>
{!currentStation[0].JrHpUrl || (
<WebSiteButton
navigate={navigate}
info={currentStation[0].JrHpUrl}
goTo={goTo}
useShow={useShow}
onExit={onExit}
/>
)}
{!currentStation[0].StationTimeTable || (
<StationTimeTableButton
info={info}
address={currentStation[0].StationTimeTable}
usePDFView={usePDFView}
navigate={navigate}
onExit={onExit}
goTo={goTo}
useShow={useShow}
/>
)}
{!currentStation[0].StationMap || (
<StationMapButton stationMap={currentStation[0].StationMap} />
)}
{!trainBus || (
<TrainBusButton
address={trainBus.address}
press={() => {
navigate("howto", {
info: trainBus.address,
goTo,
useShow,
});
onExit();
}}
oLP={() => Linking.openURL(currentStation[0].StationTimeTable)}
/>
</View>
)}
{currentStation &&
currentStation[0].JrHpUrl &&
currentStation[0].StationNumber != "M12" && (
<駅構内図 //児島例外/
navigate={navigate}
goTo={goTo}
useShow={useShow}
address={currentStation[0].JrHpUrl}
onExit={onExit}
/>
)}
{currentStation && (
<View style={{ flexDirection: "row" }}>
{!currentStation[0].JrHpUrl || (
<WebSiteButton
navigate={navigate}
info={currentStation[0].JrHpUrl}
goTo={goTo}
useShow={useShow}
onExit={onExit}
/>
)}
{!currentStation[0].StationTimeTable || (
<StationTimeTableButton
info={info}
address={currentStation[0].StationTimeTable}
usePDFView={usePDFView}
navigate={navigate}
onExit={onExit}
goTo={goTo}
useShow={useShow}
/>
)}
{!currentStation[0].StationMap || (
<StationMapButton stationMap={currentStation[0].StationMap} />
)}
{!trainBus || (
<TrainBusButton
address={trainBus.address}
press={() => {
navigate("howto", {
info: trainBus.address,
goTo,
useShow,
});
onExit();
}}
/>
)}
</View>
onExit();
}}
/>
)}
</View>
</>
)}
</View>
</View>

View File

@ -0,0 +1,129 @@
import Sign from "@/components/駅名表/Sign";
import React, { useEffect, useRef, useState } from "react";
import { AS } from "@/storageControl";
import { useWindowDimensions, View, LayoutAnimation } 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,
}) => {
const carouselRef = useRef<ICarouselInstance>(null);
const { height, width } = useWindowDimensions();
const [dotButton, setDotButton] = useState(false);
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");
});
}, []);
useEffect(() => {
if (!carouselRef.current) return;
carouselRef?.current.scrollTo({
count: listIndex - carouselRef.current.getCurrentIndex(),
animated: true,
});
}, [listIndex]);
const RenderItem = ({ item, index }) => {
return (
<View
style={{
backgroundColor: "#0000",
width,
flexDirection: "row",
marginLeft: 0,
marginRight: 0,
}}
key={item[0].StationNumber}
>
<View style={{ flex: 1 }} />
<Sign
stationID={item[0].StationNumber}
isCurrentStation={item == nearPositionStation}
oP={oPSign}
oLP={oLPSign}
/>
<View style={{ flex: 1 }} />
</View>
);
};
return (
<View style={{ flex: 1, paddingTop: 10 }}>
<Carousel
ref={carouselRef}
data={listUpStation}
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,
}}
onSnapToItem={setListIndex}
renderItem={RenderItem}
/>
<View
style={{
flexDirection: "row",
justifyContent: "center",
alignContent: "center",
alignItems: "center",
}}
>
{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}
/>
);
})}
</View>
</View>
);
};

View File

@ -0,0 +1,142 @@
import { AS } from "@/storageControl";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import React, { useEffect, useRef } from "react";
import { View, TouchableOpacity, Text, LayoutAnimation } from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
export const CarouselTypeChanger = ({
locationStatus,
position,
mapsRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation,
mapMode,
setMapMode,
}) => {
const tabBarHeight = useBottomTabBarHeight();
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 (
<View style={{ width: "100%", height: 40, flexDirection: "row", position: mapMode ? "absolute" : "relative", bottom: mapMode ? 0 : undefined}} key={"carouselTypeChanger"} >
<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>
</View>
);
};

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

@ -1,18 +1,25 @@
import { View, TouchableOpacity, Linking } from "react-native";
import AutoHeightImage from "react-native-auto-height-image";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
import { View, TouchableOpacity, Linking,Platform, Image, useWindowDimensions } from "react-native";
import Constants from "expo-constants";
export const TitleBar = () => {
const { width } = useWindowDimensions();
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
onPress={() => Linking.openURL("https://www.jr-shikoku.co.jp")}
>
<AutoHeightImage
source={require("../../assets/Header.png")}
resizeMode="contain"
width={wp("100%")}
/>
<Image source={require("../../assets/Header.png")} style={{ width: width, resizeMode: "contain", backgroundColor: "white", height: 80 }} />
</TouchableOpacity>
</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

@ -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

@ -16,6 +16,8 @@ import { TrainPosition } from "./LED_inside_Component/TrainPosition";
import { TrainPositionDataPush } from "./LED_inside_Component/TrainPositionDataPush";
import { TrainPositionDataDelete } from "./LED_inside_Component/TrainPositionDataDelete";
import { useStationList } from "../../stateBox/useStationList";
import useInterval from "@/lib/useInterval";
import dayjs from "dayjs";
type Props = {
d: {
@ -117,26 +119,46 @@ export const EachData: FC<Props> = (props) => {
const [descInput, setDescInput] = useState("");
const [stationInput, setStationInput] = 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 (
<>
<TrainPositionDataDelete
dialog={deleteDialog}
setDialog={setDeleteDialog}
currentTrainData={currentTrainData}
stationInput={stationInput}
stationNumberInput={stationNumberInput}
{...{ currentTrainData, stationInput, stationNumberInput }}
/>
<TrainPositionDataPush
dialog={dialog}
setDialog={setDialog}
currentTrainData={currentTrainData}
stationInput={stationInput}
stationNumberInput={stationNumberInput}
posInput={posInput}
descInput={descInput}
setPosInput={setPosInput}
setDescInput={setDescInput}
station={station}
{...{
currentTrainData,
stationInput,
stationNumberInput,
posInput,
descInput,
setPosInput,
setDescInput,
station,
}}
/>
<TouchableOpacity
style={{
@ -147,8 +169,10 @@ export const EachData: FC<Props> = (props) => {
marginHorizontal: "3%",
backgroundColor: "#000",
flexDirection: "row",
opacity: isShow ? 1 : 0.5,
}}
onPress={() => openTrainInfo(d)}
key={ d.train + "-eachData" }
>
<TrainName
trainName={train.trainName}
@ -161,6 +185,9 @@ export const EachData: FC<Props> = (props) => {
<DependTime time={d.time} />
<StatusAndDelay trainDelayStatus={trainDelayStatus} />
</TouchableOpacity>
{!!isDepartureNow && (
<Description info={d.lastStation == "当駅止" ? "この列車は当駅止です。間もなく到着します。":"列車の出発時刻です。"} key={d.train + "-description"} />
)}
{trainDescriptionSwitch && (
<TrainPosition
trainIDSwitch={trainIDSwitch}
@ -179,7 +206,7 @@ export const EachData: FC<Props> = (props) => {
/>
)}
{trainDescriptionSwitch && !!train.info && (
<Description info={train.info} />
<Description info={train.info} key={d.train + "-description"} />
)}
</>
);

View File

@ -3,8 +3,8 @@ import { useCurrentTrain } from "../../../stateBox/useCurrentTrain";
import LottieView from "lottie-react-native";
import { Ionicons } from "@expo/vector-icons";
export const Header = ({ getCurrentTrain }) => {
const { currentTrainLoading, setCurrentTrainLoading } = useCurrentTrain();
export const Header = () => {
const { currentTrainLoading, setCurrentTrainLoading,getCurrentTrain } = useCurrentTrain();
return (
<View
style={{

View File

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

View File

@ -1,9 +1,22 @@
import React, { CSSProperties, FC } from "react";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
import { Platform, Text, TextStyle, View, ViewStyle } from "react-native";
import {
Platform,
Text,
TextStyle,
useWindowDimensions,
View,
ViewStyle,
} from "react-native";
import { StationName } from "./StationName";
import lineColorList from "../../assets/originData/lineColorList";
export const NextPreStationLine = ({ nexStation, preStation, isMatsuyama }) => {
const 下枠フレーム: ViewStyle = {
flex: 1,
flexDirection: "row",
alignContent: "center",
alignItems: "center",
};
return (
<View style={}>
<View style={}>
@ -49,12 +62,40 @@ type FCimport = {
children: string;
};
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>;
};
const BottomStationNumberView: FC<FCimport> = ({ isMatsuyama, children }) => {
const { width } = useWindowDimensions();
const lineID = children.slice(0, 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 (
<View
style={{
@ -77,37 +118,3 @@ const BottomStationNumberView: FC<FCimport> = ({ isMatsuyama, children }) => {
</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 { View, Text, TouchableOpacity } from "react-native";
import { widthPercentageToDP as wp } from "react-native-responsive-screen";
import {
View,
Text,
TouchableOpacity,
useWindowDimensions,
} from "react-native";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import LottieView from "lottie-react-native";
import { useInterval } from "../../lib/useInterval";
@ -15,7 +19,14 @@ import { AddressText } from "./AddressText";
import { useStationList } from "../../stateBox/useStationList";
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 [nexPrePosition, setNexPrePosition] = useState(0);
const { originalStationList } = useStationList();
@ -26,48 +37,40 @@ export default function Sign(props) {
useLayoutEffect(() => {
const isFavorite = favoriteStation.filter((d) => {
const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation);
if (compare === current) {
return true;
} else {
return false;
}
const current = JSON.stringify(currentStationData);
return compare === current;
});
setTestButtonStatus(isFavorite.length == 0 ? false : true);
}, [favoriteStation, currentStation]);
}, [favoriteStation, currentStationData]);
useEffect(() => {
const isFavorite = favoriteStation.filter((d) => {
const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation);
if (compare === current) {
return true;
} else {
return false;
}
const current = JSON.stringify(currentStationData);
return compare === current;
});
setTestButtonStatus(isFavorite.length == 0 ? false : true);
}, [favoriteStation, currentStation]);
}, [favoriteStation, currentStationData]);
useInterval(() => {
if (currentStation.length == 1) {
if (currentStationData.length == 1) {
setNexPrePosition(0);
return () => {};
}
setNexPrePosition(
nexPrePosition + 1 == currentStation.length ? 0 : nexPrePosition + 1
nexPrePosition + 1 == currentStationData.length ? 0 : nexPrePosition + 1
);
}, 2000);
useEffect(() => {
setNexPrePosition(0);
getPreNextStation(currentStation[0]);
if (currentStation.length == 1) return () => {};
getPreNextStation(currentStation[1]);
}, [currentStation]);
getPreNextStation(currentStationData[0]);
if (currentStationData.length == 1) return () => {};
getPreNextStation(currentStationData[1]);
}, [currentStationData]);
useEffect(() => {
if (!currentStation[nexPrePosition]) return () => {};
getPreNextStation(currentStation[nexPrePosition]);
if (!currentStationData[nexPrePosition]) return () => {};
getPreNextStation(currentStationData[nexPrePosition]);
}, [nexPrePosition]);
const getPreNextStation = (now) => {
const lineList = [
@ -101,25 +104,81 @@ export default function Sign(props) {
if (returnData[1]) setNexStation(returnData[1]);
}
};
const isMatsuyama = currentStation[0].StationNumber == "Y55";
const isMatsuyama = currentStationData[0].StationNumber == "Y55";
//const isMatsuyama = true;
const favoliteChanger = () => {
if (testButtonStatus) {
const otherData = favoriteStation.filter((d) => {
const compare = JSON.stringify(d);
const current = JSON.stringify(currentStation);
const current = JSON.stringify(currentStationData);
return compare !== current;
});
AS.setItem("favoriteStation", JSON.stringify(otherData));
setFavoriteStation(otherData);
} else {
let ret = favoriteStation;
ret.push(currentStation);
ret.push(currentStationData);
AS.setItem("favoriteStation", JSON.stringify(ret));
setFavoriteStation(ret);
}
setTestButtonStatus(!testButtonStatus);
};
const styleSheet = {
外枠: {
width: width * 0.8,
height: ((width * 0.8) / 20) * 9,
borderColor: "#0099CC",
borderWidth: 1,
backgroundColor: "white",
},
外枠B: {
width: width * 0.8,
height: ((width * 0.8) / 20) * 9,
borderWidth: 0,
},
下帯: {
position: "absolute",
bottom: "8%",
left: "0%",
width: "100%",
height: "27%",
backgroundColor: "#0099CC",
},
下帯B: {
position: "absolute",
bottom: "0%",
left: "0%",
width: "100%",
height: "26%",
backgroundColor: "#454545",
},
JRStyle: {
position: "absolute",
top: "2%",
left: "2%",
fontWeight: "bold",
fontSize: parseInt("25%"),
color: "#0099CC",
},
下帯内容: {
position: "absolute",
bottom: "8%",
height: "27%",
width: "100%",
alignItems: "center",
flexDirection: "column",
},
下帯内容B: {
position: "absolute",
bottom: "0%",
height: "26%",
width: "100%",
alignItems: "center",
flexDirection: "column",
},
};
return (
<TouchableOpacity
style={styleSheet[isMatsuyama ? "外枠B" : "外枠"]}
@ -131,15 +190,19 @@ export default function Sign(props) {
autoPlay
loop
style={{
width: wp("80%"),
height: (wp("80%") / 20) * 9,
width: width * 0.8,
height: ((width * 0.8) / 20) * 9,
backgroundColor: "#fff",
}}
source={require("../../assets/StationSign.json")}
/>
)}
<StationNumberMaker {...{ currentStation, isMatsuyama }} />
<StationNameArea {...{ currentStation, isMatsuyama }} />
<StationNumberMaker
{...{ currentStation: currentStationData, isMatsuyama }}
/>
<StationNameArea
{...{ currentStation: currentStationData, isMatsuyama }}
/>
{isCurrentStation ? (
<View style={{ position: "absolute", right: 0, top: 0 }}>
<MaterialCommunityIcons
@ -163,62 +226,7 @@ export default function Sign(props) {
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"]}>
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
</View>
<AddressText {...{ currentStation, isMatsuyama }} />
<AddressText {...{ currentStation: currentStationData, isMatsuyama }} />
</TouchableOpacity>
);
}
const styleSheet = {
外枠: {
width: wp("80%"),
height: (wp("80%") / 20) * 9,
borderColor: "#0099CC",
borderWidth: 1,
backgroundColor: "white",
},
外枠B: {
width: wp("80%"),
height: (wp("80%") / 20) * 9,
borderWidth: 0,
},
下帯: {
position: "absolute",
bottom: "8%",
left: "0%",
width: "100%",
height: "27%",
backgroundColor: "#0099CC",
},
下帯B: {
position: "absolute",
bottom: "0%",
left: "0%",
width: "100%",
height: "26%",
backgroundColor: "#454545",
},
JRStyle: {
position: "absolute",
top: "2%",
left: "2%",
fontWeight: "bold",
fontSize: parseInt("25%"),
color: "#0099CC",
},
下帯内容: {
position: "absolute",
bottom: "8%",
height: "27%",
width: "100%",
alignItems: "center",
flexDirection: "column",
},
下帯内容B: {
position: "absolute",
bottom: "0%",
height: "26%",
width: "100%",
alignItems: "center",
flexDirection: "column",
},
};

View File

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

614
menu.js
View File

@ -1,71 +1,101 @@
import React, { useRef, useState, useEffect } from "react";
import Carousel from "react-native-reanimated-carousel";
import {
Platform,
View,
ScrollView,
Linking,
Text,
TouchableOpacity,
useWindowDimensions,
LayoutAnimation,
Dimensions,
} from "react-native";
import Constants from "expo-constants";
import * as Location from "expo-location";
import {
configureReanimatedLogger,
ReanimatedLogLevel,
} from "react-native-reanimated";
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 Sign from "./components/駅名表/Sign";
import { TitleBar } from "./components/Menu/TitleBar";
import { FixedContentBottom } from "./components/Menu/FixedContentBottom";
import { UsefulBox } from "./components/atom/UsefulBox";
import { lineList } from "./lib/getStationList";
import useInterval from "./lib/useInterval";
import { HeaderConfig } from "./lib/HeaderConfig";
import { useFavoriteStation } from "./stateBox/useFavoriteStation";
import { SheetManager } from "react-native-actions-sheet";
import { useTrainDelayData } from "./stateBox/useTrainDelayData";
import { useNavigation } from "@react-navigation/native";
import { useStationList } from "./stateBox/useStationList";
import { StationNumber } from "./components/Menu/StationPagination";
import lineColorList from "./assets/originData/lineColorList";
import { TopMenuButton } from "@/components/Menu/TopMenuButton";
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 { SimpleDot } from "./components/Menu/SimpleDot";
import { useAllTrainDiagram } from "./stateBox/useAllTrainDiagram";
export default function Menu({ getCurrentTrain }) {
const { navigate } = useNavigation();
configureReanimatedLogger({
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 { originalStationList } = useStationList();
//位置情報
const [locationStatus, setLocationStatus] = useState(null);
const { height, width } = useWindowDimensions();
const { bottom, left, right, top } = useSafeAreaInsets();
const tabBarHeight = useBottomTabBarHeight();
const [stationListMode, setStationListMode] = useState(
/*<"position"|"favorite">*/ "position"
);
useEffect(() => {
if (Platform.OS == "web") return;
Location.requestForegroundPermissionsAsync().then((data) => {
setLocationStatus(
Platform.OS == "ios"
? data.status == "granted"
: data.android.accuracy == "fine"
);
});
AS.getItem("stationListMode")
.then((res) => setStationListMode(res))
.catch((e) => {
// AS.setItem("stationListMode", "position");
});
}, []);
const getCurrentPosition = () => {
if (!locationStatus) return () => {};
Location.getCurrentPositionAsync({}).then((location) =>
makeCurrentStation(location)
);
const mapsRef = useRef(null);
const returnToTop = (bool = true) => {
scrollRef.current.scrollTo({
y: mapHeight > 80 ? mapHeight - 80 : 0,
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) => {
if (!originalStationList) return () => {};
const findStationEachLine = (selectLine) => {
const searchArea = 0.002;
const searchArea = 0.055; //検索範囲
const _calcDistance = (from, to) => {
let lat = Math.abs(from.lat - to.lat);
let lng = Math.abs(from.lng - to.lng);
@ -78,94 +108,98 @@ export default function Menu({ getCurrentTrain }) {
lng: location.coords.longitude,
}) < 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;
};
let returnDataBase = lineList
let _stList = lineList
.map((d) => findStationEachLine(originalStationList[d]))
.filter((d) => d.length > 0)
.reduce((pre, current) => {
pre.push(...current);
return pre;
}, []);
if (returnDataBase.length) {
let currentStation = currentStation == undefined ? [] : currentStation;
if (currentStation.toString() != returnDataBase.toString()) {
setCurrentStation(returnDataBase);
}
} else {
setCurrentStation(undefined);
if (_stList.length == 0) setNearPositionStation([]);
else {
let returnData = [];
_stList.forEach((d, index, array) => {
const stationName = d.Station_JP;
if (returnData.findIndex((d) => d[0].Station_JP == stationName) != -1)
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]);
useInterval(getCurrentPosition, 5000);
const [nearPositionStation, setNearPositionStation] = useState([]); //第三要素
const [currentStation, setCurrentStation] = useState(undefined); //第三要素
const [listIndex, setListIndex] = useState(0);
const carouselRef = useRef();
const [selectedCurrentStation, setSelectedCurrentStation] = useState(0);
const [allStationData, setAllStationData] = useState([]);
const [listUpStation, setListUpStation] = useState([]);
useEffect(() => {
setAllStationData(
[currentStation, ...favoriteStation].filter((d) => d != undefined)
);
}, [currentStation, favoriteStation]);
if (stationListMode == "position") {
setListUpStation(nearPositionStation.filter((d) => d != undefined));
} else {
setListUpStation(favoriteStation.filter((d) => d != undefined));
}
}, [nearPositionStation, favoriteStation, stationListMode]);
useEffect(() => {
if (allStationData.length == 0) {
setSelectedCurrentStation(0);
if (listUpStation.length == 0) {
setListIndex(0);
return;
}
if (allStationData[selectedCurrentStation] == undefined) {
const count = selectedCurrentStation - 1;
setSelectedCurrentStation(count);
if (listUpStation[listIndex] == undefined) {
const count = listIndex - 1;
setMapMode(false);
setListIndex(count);
}
}, [selectedCurrentStation, currentStation, allStationData]);
}, [listIndex, nearPositionStation, listUpStation]);
useEffect(() => {
if (!carouselRef.current) return;
carouselRef?.current.scrollTo({
count: selectedCurrentStation - carouselRef.current.getCurrentIndex(),
animated: true,
});
}, [selectedCurrentStation]);
//全列車ダイヤリストを作成するuseEffect
const { allTrainDiagram:trainDiagram} = useAllTrainDiagram();
const oPSign = () => {
const payload = {
currentStation:
originalStationList &&
allStationData.length != 0 &&
allStationData[selectedCurrentStation],
navigate: navigate,
goTo: "menu",
useShow: () => SheetManager.show("StationDetailView", { payload }),
onExit: () => SheetManager.hide("StationDetailView"),
if (originalStationList == undefined) return;
if (listUpStation.length == 0) return;
if (listUpStation[listIndex] == undefined) return;
const { lat, lng } = listUpStation[listIndex][0];
const mapRegion = {
latitude: lat,
longitude: lng,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
};
SheetManager.show("StationDetailView", { payload });
};
if (mapMode) return;
mapsRef.current.animateToRegion(mapRegion, 1000);
}, [listIndex, nearPositionStation, listUpStation, mapsRef]);
const [dotButton, setDotButton] = useState(false);
useEffect(() => {
AS.getItem("CarouselSettings/activeDotSettings").then((data) => {
setDotButton(data === "true");
});
}, []);
const oLPSign = () => {
LayoutAnimation.configureNext({
duration: 600,
update: { type: "spring", springDamping: 0.5 },
});
AS.setItem(
"CarouselSettings/activeDotSettings",
!dotButton ? "true" : "false"
);
setDotButton(!dotButton);
};
const width = Dimensions.get("window").width;
return (
<View
style={{
@ -176,246 +210,148 @@ export default function Menu({ getCurrentTrain }) {
>
<StatusbarDetect />
<TitleBar />
<ScrollView>
<TopMenuButton />
{originalStationList.length != 0 && allStationData.length != 0 && (
<View style={{ flex: 1, paddingTop: 10 }}>
<Carousel
ref={carouselRef}
data={originalStationList && allStationData}
height={(wp("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,
<ScrollView
ref={scrollRef}
snapToStart={false}
snapToEnd={false}
decelerationRate={"normal"}
snapToOffsets={[mapHeight - 80]}
onScrollBeginDrag={onScrollBeginDrag}
onScrollEndDrag={(e) => {
console.log(e.nativeEvent.velocity);
if (e.nativeEvent.contentOffset.y < mapHeight - 80) {
if (scrollStartPosition > e.nativeEvent.contentOffset.y) {
goToMap();
} else {
returnToTop();
}
}
}}
>
<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),
}}
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>
);
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();
}}
/>
<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>
))}
</MapView>
{!mapMode && (
<CarouselTypeChanger
{...{
locationStatus,
position,
mapsRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation: setListIndex,
mapMode,
setMapMode,
}}
/>
)}
{allStationData.length != 0 &&
originalStationList.length != 0 &&
allStationData[selectedCurrentStation] && (
<LED_vision
station={
originalStationList && allStationData[selectedCurrentStation]
}
trainDiagram={trainDiagram}
getCurrentTrain={getCurrentTrain}
navigate={navigate}
openStationACFromEachTrainInfo={() => {}}
{listUpStation.length != 0 && originalStationList.length != 0 && (
<>
<CarouselBox
{...{
originalStationList,
listUpStation,
nearPositionStation,
setListIndex,
listIndex,
navigate,
}}
/>
)}
{listUpStation[listIndex] && (
<LED_vision
station={listUpStation[listIndex]}
navigate={navigate}
openStationACFromEachTrainInfo={() => {}}
/>
)}
</>
)}
<TopMenuButton />
<JRSTraInfoBox />
<FixedContentBottom navigate={navigate} />
</ScrollView>
{mapMode && (
<CarouselTypeChanger
{...{
locationStatus,
position,
mapsRef,
stationListMode,
setStationListMode,
setSelectedCurrentStation: setListIndex,
mapMode,
setMapMode,
}}
/>
)}
</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

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

View File

@ -10,13 +10,15 @@ import { lineList, getStationList } from "../lib/getStationList";
type initialStateType = {
originalStationList: any[][];
setOriginalStationList: React.Dispatch<React.SetStateAction<any[]>>;
getStationData: (id: string) => void;
getStationDataFromName: (id: string) => any[];
getStationDataFromId: (id: string) => any[];
stationList: any[];
};
const initialState = {
originalStationList: [[]],
setOriginalStationList: () => {},
getStationData: () => {},
getStationDataFromName: () => [],
getStationDataFromId: () => [],
stationList: [],
};
@ -33,7 +35,18 @@ export const StationListProvider: FC<Props> = ({ children }) => {
useEffect(() => {
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 = [];
Object.keys(originalStationList).forEach((key) => {
originalStationList[key].forEach((station) => {
@ -48,19 +61,18 @@ export const StationListProvider: FC<Props> = ({ children }) => {
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)
setStationList(stationList);
},[originalStationList])
return (
<StationListContext.Provider
value={{ originalStationList, setOriginalStationList, getStationData, stationList }}
value={{ originalStationList, setOriginalStationList, getStationDataFromName, getStationDataFromId, stationList }}
>
{children}
</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(),
setGetTime: () => {},
loadingDelayData: true,
setLoadingDelayData: () => {},
setLoadingDelayData: (loading) => {},
delayData: undefined,
setDelayData: () => {},
};

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>
);
};