Compare commits
269 Commits
patch/6.x
...
fix/some-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131bd30984 | ||
|
|
af4e0d9438 | ||
|
|
ec9b6dd1bc | ||
|
|
e1c12c9dab | ||
|
|
ad75dfe27c | ||
|
|
9c926362a9 | ||
|
|
0a1940f781 | ||
|
|
f937bcc22d | ||
|
|
69f2d38a0a | ||
|
|
1d9a1d593b | ||
|
|
b3d7ba448d | ||
|
|
3cb14405f6 | ||
|
|
33defa1182 | ||
|
|
1084b6b299 | ||
|
|
fbb8580d28 | ||
|
|
5db74714db | ||
|
|
5a106a3ab7 | ||
|
|
0ef169518a | ||
|
|
9d76264a28 | ||
|
|
1181e488c1 | ||
|
|
bbfd82aaea | ||
|
|
bd10290bb3 | ||
|
|
63431adab1 | ||
|
|
393bcc4df3 | ||
|
|
ec53d4fa2a | ||
|
|
dd62ad8f73 | ||
|
|
25e13b9f41 | ||
|
|
b9f8ed1ea8 | ||
|
|
2b4b237d1a | ||
|
|
a69b59ed84 | ||
|
|
49c69ffe53 | ||
|
|
040ce9bce1 | ||
|
|
06b2e97392 | ||
|
|
b8481e985e | ||
|
|
146602caa1 | ||
|
|
c07f8f1677 | ||
|
|
8a318475bf | ||
|
|
f5abf0d85a | ||
|
|
e373ffdb76 | ||
|
|
61074d0bfe | ||
|
|
9a03143853 | ||
|
|
3ca109edee | ||
|
|
5420531c64 | ||
|
|
8116ecd197 | ||
|
|
1ecd67d8c5 | ||
|
|
6dd24b36ec | ||
|
|
84b043ff5f | ||
|
|
5cf864b9ab | ||
|
|
43758aa781 | ||
|
|
684a184d40 | ||
|
|
5e66fab175 | ||
|
|
e1293d2500 | ||
|
|
8ce0244c4b | ||
|
|
50e514543b | ||
|
|
e93cc93b77 | ||
|
|
bf5be9bd57 | ||
|
|
9e45d592b1 | ||
|
|
834ad2bd41 | ||
|
|
83ca18f2c7 | ||
|
|
baacfd5855 | ||
|
|
066317bbc8 | ||
|
|
0bcb03f833 | ||
|
|
9037d21237 | ||
|
|
4789543573 | ||
|
|
5345dca95f | ||
|
|
18f11c88b1 | ||
|
|
cee238d060 | ||
|
|
dad462ff45 | ||
|
|
26cde03d3e | ||
|
|
0230b56f8b | ||
|
|
a2912d77ae | ||
|
|
e80eeae211 | ||
|
|
33410fcd61 | ||
|
|
ff25363600 | ||
|
|
36cd7448a5 | ||
|
|
4a0e252366 | ||
|
|
1c96776f56 | ||
|
|
53b95a91d5 | ||
|
|
82fe8d1244 | ||
|
|
80eeb51134 | ||
|
|
2d96bdcad9 | ||
|
|
59653bbc16 | ||
|
|
f34d06192b | ||
|
|
16bf96faf7 | ||
|
|
3925370b97 | ||
|
|
dcd8de06f8 | ||
|
|
1d57f2a5c6 | ||
|
|
d3e4b173c7 | ||
|
|
5202f35702 | ||
|
|
dc3d250466 | ||
|
|
3f6b3cfcfb | ||
|
|
2cd5142262 | ||
|
|
4b518b848e | ||
|
|
fb89a2b334 | ||
|
|
a66af59438 | ||
|
|
04d0f50b99 | ||
|
|
f4b86f4e77 | ||
|
|
91470b5db8 | ||
|
|
ffcc6ff660 | ||
|
|
681b12b622 | ||
|
|
13f2c4de7a | ||
|
|
f19600a3af | ||
|
|
9a567d2486 | ||
|
|
9271629aa9 | ||
|
|
d79f5a07f8 | ||
|
|
86a4428861 | ||
|
|
960acdbb3f | ||
|
|
86123ecb81 | ||
|
|
1d14bcf91a | ||
|
|
c7b1501475 | ||
|
|
814de31418 | ||
|
|
ecc9ee313e | ||
|
|
0a2333a201 | ||
|
|
b1a8a4c98f | ||
|
|
0d45732d66 | ||
|
|
50fd2ece64 | ||
|
|
38171574e8 | ||
|
|
f972d2b719 | ||
|
|
ce4fb4d1da | ||
|
|
deb24caaa2 | ||
|
|
39a5b33e77 | ||
|
|
5515f42415 | ||
|
|
6829744fa4 | ||
|
|
fbfb83fa34 | ||
|
|
777b5c8acb | ||
|
|
8eb49f57d6 | ||
|
|
30e4e9780a | ||
|
|
c5d4dc3b65 | ||
|
|
d9574f991d | ||
|
|
7f2480bc01 | ||
|
|
a665bf3a74 | ||
|
|
b8372e5087 | ||
|
|
2cdcb5176b | ||
|
|
75c07f013d | ||
|
|
44cb462595 | ||
|
|
46bfea4e13 | ||
|
|
9bf7a735c1 | ||
|
|
5a2dc8c6a8 | ||
|
|
bdffce9e6a | ||
|
|
06650d014a | ||
|
|
d4ad8c005e | ||
|
|
45feeece58 | ||
|
|
0d0b82eee1 | ||
|
|
385c2d8b86 | ||
|
|
3e26463354 | ||
|
|
41cc70086a | ||
|
|
cefca15de9 | ||
|
|
98c71127aa | ||
|
|
62c84f153e | ||
|
|
d049d3b07d | ||
|
|
83741e32fc | ||
|
|
8484f15092 | ||
|
|
f262226d4c | ||
|
|
1afa5e4377 | ||
|
|
032bcf127e | ||
|
|
9f6d86b8b6 | ||
|
|
787718c36a | ||
|
|
72e7d63bd7 | ||
|
|
5457ced33d | ||
|
|
b243439e78 | ||
|
|
91cad9c2c8 | ||
|
|
65f3b2a877 | ||
|
|
11cd8e0f40 | ||
|
|
925d162f26 | ||
|
|
7c84b037ac | ||
|
|
731fe504c6 | ||
|
|
676460353f | ||
|
|
942ec395f1 | ||
|
|
3f1da7272f | ||
|
|
306cf6882e | ||
|
|
ea261f4bbb | ||
|
|
468bb4633a | ||
|
|
e032dc9d70 | ||
|
|
2827fce560 | ||
|
|
bf4a59149a | ||
|
|
cf611c6c8d | ||
|
|
b7a09eda6e | ||
|
|
10df37d0a2 | ||
|
|
58b2049b24 | ||
|
|
29bc89f183 | ||
|
|
adfe69b72f | ||
|
|
f387479ff7 | ||
|
|
684aaeb92f | ||
|
|
0a8d5ca2b6 | ||
|
|
48b38a2fa3 | ||
|
|
aeb043cac5 | ||
|
|
2c6ceb73d8 | ||
|
|
f214f45fef | ||
|
|
616846e1cd | ||
|
|
be88a46df1 | ||
|
|
7386ec09fc | ||
|
|
a068dabc75 | ||
|
|
8fda56793a | ||
|
|
83c7dbde63 | ||
|
|
676fbf7b64 | ||
|
|
8bc726628a | ||
|
|
ee22d21862 | ||
|
|
3894694c9b | ||
|
|
ec4db3de9b | ||
|
|
6a66429431 | ||
|
|
c2d3645b86 | ||
|
|
ea94e4cf0d | ||
|
|
4a7e481bfd | ||
|
|
720123b1e5 | ||
|
|
faf452166c | ||
|
|
0eb7d70caa | ||
|
|
4296eada04 | ||
|
|
983d48a1fe | ||
|
|
556f98faac | ||
|
|
08e052f291 | ||
|
|
ab92cc7a85 | ||
|
|
d50d77aa44 | ||
|
|
3ea8300846 | ||
|
|
beb9b21e1c | ||
|
|
7d7b1849dd | ||
|
|
30d1111768 | ||
|
|
cc15e6a1ee | ||
|
|
66650764df | ||
|
|
191dd76627 | ||
|
|
affe907dfd | ||
|
|
50822c6c74 | ||
|
|
2142d90141 | ||
|
|
9cc7b0d4af | ||
|
|
0917bc0a74 | ||
|
|
0f52441b17 | ||
|
|
bf27904d7c | ||
|
|
3a182d4650 | ||
|
|
a16588c70f | ||
|
|
cf1b2f763e | ||
|
|
381873b926 | ||
|
|
9c14a871e8 | ||
|
|
d7f227d5e5 | ||
|
|
c1accbb204 | ||
|
|
ac2548e7b6 | ||
|
|
87f1cf2b1e | ||
|
|
c49aeeb331 | ||
|
|
506dc7157e | ||
|
|
66f5744d51 | ||
|
|
d4a9c4d7d8 | ||
|
|
f2d0b060b6 | ||
|
|
38191be0d3 | ||
|
|
df2e4145a2 | ||
|
|
d6ab19d4b1 | ||
|
|
7b7ec45bfa | ||
|
|
29941f515f | ||
|
|
625ee1d786 | ||
|
|
a9bb366308 | ||
|
|
657ee7494b | ||
|
|
b60a43f25c | ||
|
|
7004eeefad | ||
|
|
413ef4acb3 | ||
|
|
7f3a1493ef | ||
|
|
8e64932a01 | ||
|
|
9036e7a8c1 | ||
|
|
1bf4a6991d | ||
|
|
03b9080c06 | ||
|
|
4952e32e65 | ||
|
|
ff46c6ac8f | ||
|
|
70bbb4ed5a | ||
|
|
0a4c61071d | ||
|
|
7a58a9524a | ||
|
|
6bcb3fcaf1 | ||
|
|
d921d7f8b6 | ||
|
|
0a677c908d | ||
|
|
a42c0871bd | ||
|
|
4eea97ed1f | ||
|
|
5b0de88218 | ||
|
|
765b0d72b7 | ||
|
|
0e9b049707 |
10
.gitignore
vendored
@@ -4,7 +4,7 @@ node_modules/**/*
|
||||
.pnp.js
|
||||
|
||||
# Expo
|
||||
.expo/*
|
||||
.expo/
|
||||
.expo-shared
|
||||
|
||||
# Build outputs
|
||||
@@ -52,3 +52,11 @@ Thumbs.db
|
||||
*.log
|
||||
*.tmp
|
||||
.cache/
|
||||
|
||||
android/
|
||||
!modules/**/android/
|
||||
ios/
|
||||
!modules/**/ios/
|
||||
*.ipa
|
||||
*.apk
|
||||
*.aab
|
||||
141
App.tsx
@@ -1,5 +1,10 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Platform, UIManager } from "react-native";
|
||||
import {
|
||||
IS_LOW_DENSITY,
|
||||
DEX_SCALE,
|
||||
useRealWindowDimensions,
|
||||
} from "./utils/dexDimensionOverride";
|
||||
import { Linking, Platform, UIManager, View } from "react-native";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import "./utils/disableFontScaling"; // グローバルなフォントスケーリング無効化
|
||||
import { AppContainer } from "./Apps";
|
||||
@@ -10,7 +15,7 @@ import { CurrentTrainProvider } from "./stateBox/useCurrentTrain";
|
||||
import { AreaInfoProvider } from "./stateBox/useAreaInfo";
|
||||
import { BusAndTrainDataProvider } from "./stateBox/useBusAndTrainData";
|
||||
import { AllTrainDiagramProvider } from "./stateBox/useAllTrainDiagram";
|
||||
import { SheetProvider } from "react-native-actions-sheet";
|
||||
import { SheetProvider, SheetManager } from "react-native-actions-sheet";
|
||||
import "./components/ActionSheetComponents/sheets";
|
||||
import { TrainDelayDataProvider } from "./stateBox/useTrainDelayData";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
@@ -20,6 +25,9 @@ import { buildProvidersTree } from "./lib/providerTreeProvider";
|
||||
import { StationListProvider } from "./stateBox/useStationList";
|
||||
import { NotificationProvider } from "./stateBox/useNotifications";
|
||||
import { UserPositionProvider } from "./stateBox/useUserPosition";
|
||||
import { rootNavigationRef } from "./lib/rootNavigation";
|
||||
import { AppThemeProvider } from "./lib/theme";
|
||||
import StatusbarDetect from "./StatusbarDetect";
|
||||
|
||||
LogBox.ignoreLogs([
|
||||
"ViewPropTypes will be removed",
|
||||
@@ -37,6 +45,85 @@ export default function App() {
|
||||
UpdateAsync();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const openFelicaPage = (retryCount = 0) => {
|
||||
if (!rootNavigationRef.isReady()) {
|
||||
if (retryCount < 8) {
|
||||
setTimeout(() => openFelicaPage(retryCount + 1), 250);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
rootNavigationRef.navigate("topMenu", {
|
||||
screen: "setting",
|
||||
params: {
|
||||
screen: "FelicaHistoryPage",
|
||||
},
|
||||
} as any);
|
||||
};
|
||||
|
||||
const navigateWhenReady = (
|
||||
callback: () => void,
|
||||
url: string,
|
||||
retryCount = 0
|
||||
) => {
|
||||
if (!rootNavigationRef.isReady()) {
|
||||
if (retryCount < 8) {
|
||||
setTimeout(() => navigateWhenReady(callback, url, retryCount + 1), 250);
|
||||
}
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const routeFromUrl = (url: string, retryCount = 0) => {
|
||||
const normalized = (url || "").toLowerCase();
|
||||
if (!normalized) return;
|
||||
|
||||
if (
|
||||
normalized.includes("felicahistorypage") ||
|
||||
normalized.includes("open/felica")
|
||||
) {
|
||||
navigateWhenReady(() => openFelicaPage(), url, retryCount);
|
||||
} else if (normalized.includes("open/traininfo")) {
|
||||
navigateWhenReady(() => {
|
||||
rootNavigationRef.navigate("topMenu", { screen: "menu" } as any);
|
||||
setTimeout(() => {
|
||||
SheetManager.show("JRSTraInfo");
|
||||
}, 450);
|
||||
}, url, retryCount);
|
||||
} else if (normalized.includes("open/operation")) {
|
||||
navigateWhenReady(() => {
|
||||
rootNavigationRef.navigate("information" as any);
|
||||
}, url, retryCount);
|
||||
} else if (normalized.includes("open/settings")) {
|
||||
navigateWhenReady(() => {
|
||||
rootNavigationRef.navigate("topMenu", {
|
||||
screen: "setting",
|
||||
} as any);
|
||||
}, url, retryCount);
|
||||
} else if (normalized.includes("open/topmenu")) {
|
||||
navigateWhenReady(() => {
|
||||
rootNavigationRef.navigate("topMenu", {
|
||||
screen: "menu",
|
||||
} as any);
|
||||
}, url, retryCount);
|
||||
}
|
||||
};
|
||||
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) routeFromUrl(url);
|
||||
});
|
||||
|
||||
const sub = Linking.addEventListener("url", ({ url }) => {
|
||||
routeFromUrl(url);
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const ProviderTree = buildProvidersTree([
|
||||
AllTrainDiagramProvider,
|
||||
NotificationProvider,
|
||||
@@ -51,14 +138,46 @@ export default function App() {
|
||||
SheetProvider,
|
||||
]);
|
||||
return (
|
||||
<DeviceOrientationChangeProvider>
|
||||
<SafeAreaProvider>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<ProviderTree>
|
||||
<AppContainer />
|
||||
</ProviderTree>
|
||||
</GestureHandlerRootView>
|
||||
</SafeAreaProvider>
|
||||
</DeviceOrientationChangeProvider>
|
||||
<AppThemeProvider>
|
||||
<DeviceOrientationChangeProvider>
|
||||
<SafeAreaProvider>
|
||||
<StatusbarDetect />
|
||||
<DensityScaleWrapper>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<ProviderTree>
|
||||
<AppContainer />
|
||||
</ProviderTree>
|
||||
</GestureHandlerRootView>
|
||||
</DensityScaleWrapper>
|
||||
</SafeAreaProvider>
|
||||
</DeviceOrientationChangeProvider>
|
||||
</AppThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 低密度ディスプレイ(DeX等)で全体を transform scale で拡大。
|
||||
* Dimensions.get をパッチ済みなので、コンポーネントは内部サイズに合わせてレイアウトする。
|
||||
*/
|
||||
function DensityScaleWrapper({ children }: { children: React.ReactNode }) {
|
||||
const { width, height } = useRealWindowDimensions();
|
||||
|
||||
if (!IS_LOW_DENSITY) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ width, height, overflow: "hidden" }}>
|
||||
<View
|
||||
style={{
|
||||
width: width / DEX_SCALE,
|
||||
height: height / DEX_SCALE,
|
||||
transform: [{ scale: DEX_SCALE }],
|
||||
transformOrigin: "top left",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
127
Apps.tsx
@@ -1,14 +1,22 @@
|
||||
import React from "react";
|
||||
import { NavigationContainer, NavigationContainerRef } from "@react-navigation/native";
|
||||
import { NavigationContainer, DarkTheme, DefaultTheme } from "@react-navigation/native";
|
||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||
import { Platform } from "react-native";
|
||||
import { Animated, Platform, ActivityIndicator, View, StyleSheet } from "react-native";
|
||||
import { useNavigationState } from "@react-navigation/native";
|
||||
import { useFonts } from "expo-font";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import TNDView from "./ndView";
|
||||
import { initIcon } from "./lib/initIcon";
|
||||
import { Top } from "./Top";
|
||||
import { MenuPage } from "./MenuPage";
|
||||
import { useAreaInfo } from "./stateBox/useAreaInfo";
|
||||
import { useTrainMenu } from "./stateBox/useTrainMenu";
|
||||
import lineColorList from "./assets/originData/lineColorList";
|
||||
import { stationIDPair } from "./lib/getStationList";
|
||||
import "./components/ActionSheetComponents/sheets";
|
||||
import { rootNavigationRef } from "./lib/rootNavigation";
|
||||
import { fixedColors } from "./lib/theme/colors";
|
||||
import { useThemeColors } from "./lib/theme";
|
||||
|
||||
type RootTabParamList = {
|
||||
positions: undefined;
|
||||
@@ -25,10 +33,60 @@ type TabProps = {
|
||||
isInfo?: boolean;
|
||||
};
|
||||
|
||||
const Tab = createBottomTabNavigator<RootTabParamList>();
|
||||
|
||||
export function AppContainer() {
|
||||
const Tab = createBottomTabNavigator<RootTabParamList>();
|
||||
const { areaInfo, areaIconBadgeText, isInfo } = useAreaInfo();
|
||||
const navigationRef = React.useRef<NavigationContainerRef<RootTabParamList>>(null);
|
||||
const { selectedLine } = useTrainMenu();
|
||||
const [isExtraWindowOpen, setIsExtraWindowOpen] = React.useState(false);
|
||||
|
||||
// フェードアニメーション用 (0=通常, 1=追加ウィンドウ青)
|
||||
const fadeAnim = React.useRef(new Animated.Value(0)).current;
|
||||
React.useEffect(() => {
|
||||
Animated.timing(fadeAnim, {
|
||||
toValue: isExtraWindowOpen ? 1 : 0,
|
||||
duration: 300,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
}, [isExtraWindowOpen]);
|
||||
|
||||
const lineColor = selectedLine && stationIDPair[selectedLine]
|
||||
? lineColorList[stationIDPair[selectedLine]]
|
||||
: null;
|
||||
|
||||
const darkenHex = (hex: string, factor: number) => {
|
||||
const h = hex.replace("#", "");
|
||||
const r = Math.round(parseInt(h.slice(0, 2), 16) * factor);
|
||||
const g = Math.round(parseInt(h.slice(2, 4), 16) * factor);
|
||||
const b = Math.round(parseInt(h.slice(4, 6), 16) * factor);
|
||||
return `#${[r, g, b].map((v) => Math.min(255, v).toString(16).padStart(2, "0")).join("")}`;
|
||||
};
|
||||
const { isDark } = useThemeColors();
|
||||
const lineColorDark = lineColor ? darkenHex(lineColor, 0.78) : null;
|
||||
const linking = {
|
||||
prefixes: ["jrshikoku://"],
|
||||
config: {
|
||||
screens: {
|
||||
positions: {
|
||||
screens: {
|
||||
Apps: "positions/apps",
|
||||
},
|
||||
},
|
||||
topMenu: {
|
||||
screens: {
|
||||
menu: "topMenu/menu",
|
||||
setting: {
|
||||
screens: {
|
||||
settingTopPage: "topMenu/setting",
|
||||
FelicaHistoryPage: "topMenu/setting/FelicaHistoryPage",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
information: "information",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getTabProps = (
|
||||
name: keyof RootTabParamList,
|
||||
@@ -53,19 +111,68 @@ export function AppContainer() {
|
||||
"JNR-font": require("./assets/fonts/JNRfont_pict.ttf"),
|
||||
"DiaPro": require("./assets/fonts/DiaPro-Regular.otf"),
|
||||
});
|
||||
if (!fontLoaded) {
|
||||
return (
|
||||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<NavigationContainer ref={navigationRef}>
|
||||
<NavigationContainer
|
||||
ref={rootNavigationRef}
|
||||
linking={linking}
|
||||
theme={isDark ? DarkTheme : DefaultTheme}
|
||||
onStateChange={(state) => {
|
||||
const hasExtra = state?.routes?.some((r) => (r.state?.index ?? 0) > 0) ?? false;
|
||||
setIsExtraWindowOpen(hasExtra);
|
||||
}}
|
||||
>
|
||||
{/* @ts-expect-error - Tab.Navigator type definition issue */}
|
||||
<Tab.Navigator
|
||||
initialRouteName="topMenu"
|
||||
screenOptions={{
|
||||
lazy: false,
|
||||
tabBarHideOnKeyboard: Platform.OS === "android",
|
||||
animation: "shift",
|
||||
screenOptions={({ route }) => {
|
||||
const showGradient = route.name === "positions" && !!lineColor && !!lineColorDark;
|
||||
const defaultBg = isDark ? "#1c1c1e" : "white";
|
||||
const defaultActive = isDark ? "#ffffff" : "#007AFF";
|
||||
const defaultInactive = isDark ? "#8e8e93" : "#8e8e93";
|
||||
return {
|
||||
lazy: false,
|
||||
tabBarHideOnKeyboard: Platform.OS === "android",
|
||||
animation: Platform.OS === "ios" ? "none" : "shift",
|
||||
sceneContainerStyle: { backgroundColor: defaultBg },
|
||||
tabBarActiveTintColor: (showGradient || isExtraWindowOpen) ? "white" : defaultActive,
|
||||
tabBarInactiveTintColor: (showGradient || isExtraWindowOpen) ? "rgba(255,255,255,0.75)" : defaultInactive,
|
||||
tabBarStyle: { backgroundColor: "transparent" },
|
||||
tabBarBackground: () => (
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* 路線カラー or デフォルト背景 */}
|
||||
{showGradient ? (
|
||||
<LinearGradient
|
||||
colors={[lineColor!, lineColorDark!]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
style={{ ...StyleSheet.absoluteFillObject }}
|
||||
/>
|
||||
) : (
|
||||
<View style={{ ...StyleSheet.absoluteFillObject, backgroundColor: defaultBg }} />
|
||||
)}
|
||||
{/* 追加ウィンドウ時の青グラデーション(フェードイン/アウト) */}
|
||||
<Animated.View style={{ ...StyleSheet.absoluteFillObject, opacity: fadeAnim }}>
|
||||
<LinearGradient
|
||||
colors={[fixedColors.primary, fixedColors.primaryDark]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 0, y: 1 }}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
),
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Tab.Screen
|
||||
{...getTabProps("positions", "走行位置", "barchart", "AntDesign")}
|
||||
{...getTabProps("positions", "走行位置", "bar-chart", "AntDesign")}
|
||||
component={Top}
|
||||
/>
|
||||
<Tab.Screen
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React, { CSSProperties } from "react";
|
||||
import { BackHandler, View, ViewProps } from "react-native";
|
||||
import { Alert, BackHandler, View, ViewProps } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import { BigButton } from "./components/atom/BigButton";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
export default ({ route }) => {
|
||||
if (!route.params) {
|
||||
return null;
|
||||
}
|
||||
const { uri, useExitButton = true } = route.params;
|
||||
const { goBack } = useNavigation();
|
||||
const { fixed } = useThemeColors();
|
||||
const webViewRef = React.useRef<WebView>(null);
|
||||
const [canGoBack, setCanGoBack] = React.useState(false);
|
||||
|
||||
@@ -23,17 +25,26 @@ export default ({ route }) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
BackHandler.addEventListener("hardwareBackPress", onHardwareBack);
|
||||
return () => BackHandler.removeEventListener("hardwareBackPress", onHardwareBack);
|
||||
const subscription = BackHandler.addEventListener("hardwareBackPress", onHardwareBack);
|
||||
return () => subscription.remove();
|
||||
}, [canGoBack, goBack])
|
||||
);
|
||||
return (
|
||||
<View style={styles}>
|
||||
<View style={{ height: "100%", backgroundColor: fixed.primary }}>
|
||||
<WebView
|
||||
source={{ uri }}
|
||||
contentMode="mobile"
|
||||
allowsBackForwardNavigationGestures
|
||||
ref={webViewRef}
|
||||
onNavigationStateChange={(navState) => setCanGoBack(navState.canGoBack)}
|
||||
onNavigationStateChange={(navState) => {
|
||||
setCanGoBack(navState.canGoBack);
|
||||
if (navState.url === "https://unyohub.2pd.jp/integration/succeeded.php") {
|
||||
goBack();
|
||||
Alert.alert("鉄道運用HUBへの投稿完了", "運用HUBからのこのアプリへのデータ反映には暫く時間がかかりますので、しばらくお待ちください。", [
|
||||
{ text: "完了" },
|
||||
]);
|
||||
}
|
||||
}}
|
||||
onMessage={(event) => {
|
||||
const { data } = event.nativeEvent;
|
||||
const { type } = JSON.parse(data);
|
||||
@@ -45,7 +56,3 @@ export default ({ route }) => {
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const styles: ViewProps["style"] = {
|
||||
height: "100%",
|
||||
backgroundColor: "#0099CC",
|
||||
};
|
||||
|
||||
29
MenuPage.tsx
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
import { useWindowDimensions, Platform } from "react-native";
|
||||
import { useWindowDimensions, Platform, useColorScheme } from "react-native";
|
||||
import Constants from "expo-constants";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
import { Dimensions, StatusBar } from "react-native";
|
||||
|
||||
@@ -27,9 +28,12 @@ const Stack = createStackNavigator();
|
||||
export function MenuPage() {
|
||||
const { favoriteStation, setFavoriteStation } = useFavoriteStation();
|
||||
const { height, width } = useWindowDimensions();
|
||||
const { verticalScale } = useResponsive();
|
||||
const tabBarHeight = useBottomTabBarHeight();
|
||||
const navigation = useNavigation<any>();
|
||||
const { addListener } = navigation;
|
||||
const isDark = useColorScheme() === "dark";
|
||||
const bgColor = isDark ? "#1c1c1e" : "#ffffff";
|
||||
useEffect(() => {
|
||||
AS.getItem(STORAGE_KEYS.START_PAGE)
|
||||
.then((res) => {
|
||||
@@ -56,15 +60,21 @@ export function MenuPage() {
|
||||
const scrollRef = useRef(null);
|
||||
const [mapMode, setMapMode] = useState(false);
|
||||
const [mapHeight, setMapHeight] = useState(0);
|
||||
const mapHeightRef = useRef(0);
|
||||
const favoriteStationRef = useRef(favoriteStation);
|
||||
useEffect(() => {
|
||||
const MapHeight =
|
||||
height -
|
||||
tabBarHeight +
|
||||
(Platform.OS == "android" ? Constants.statusBarHeight : 0) -
|
||||
100 -
|
||||
((((width / 100) * 80) / 20) * 9 + 10 + 30);
|
||||
verticalScale(100) -
|
||||
((((width / 100) * 80) / 20) * 9 + verticalScale(10) + verticalScale(30));
|
||||
setMapHeight(MapHeight);
|
||||
mapHeightRef.current = MapHeight;
|
||||
}, [height, tabBarHeight, width]);
|
||||
useEffect(() => {
|
||||
favoriteStationRef.current = favoriteStation;
|
||||
}, [favoriteStation]);
|
||||
const [MapFullHeight, setMapFullHeight] = useState(0);
|
||||
useEffect(() => {
|
||||
const MapFullHeight =
|
||||
@@ -75,15 +85,15 @@ export function MenuPage() {
|
||||
}, [height, tabBarHeight, width]);
|
||||
useEffect(() => {
|
||||
const unsubscribe = addListener("tabPress", (e) => {
|
||||
scrollRef.current.scrollTo({
|
||||
y: mapHeight - 80,
|
||||
scrollRef.current?.scrollTo({
|
||||
y: mapHeightRef.current - verticalScale(80),
|
||||
animated: true,
|
||||
});
|
||||
setMapMode(false);
|
||||
AS.getItem(STORAGE_KEYS.FAVORITE_STATION)
|
||||
.then((d) => {
|
||||
const returnData = JSON.parse(d);
|
||||
if (favoriteStation.toString() != d) {
|
||||
if (favoriteStationRef.current.toString() != d) {
|
||||
setFavoriteStation(returnData);
|
||||
}
|
||||
})
|
||||
@@ -95,9 +105,12 @@ export function MenuPage() {
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [navigation, mapHeight, favoriteStation, setFavoriteStation]);
|
||||
}, [navigation]);
|
||||
return (
|
||||
<Stack.Navigator id={null}>
|
||||
<Stack.Navigator
|
||||
id={null}
|
||||
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="menu"
|
||||
options={{
|
||||
|
||||
58
README.md
@@ -175,7 +175,63 @@ export const API_ENDPOINTS = {
|
||||
};
|
||||
```
|
||||
|
||||
## 📱 ビルド
|
||||
## <EFBFBD> 外部からのアプリ起動(ディープリンク)
|
||||
|
||||
本アプリは複数の方法で外部から起動・画面遷移できます。
|
||||
|
||||
### カスタムURLスキーム
|
||||
|
||||
スキーム `jrshikoku://` を使って特定の画面を直接開くことができます。
|
||||
|
||||
| URL | 遷移先 |
|
||||
|---|---|
|
||||
| `jrshikoku://open/felica` | FeliCa履歴ページ |
|
||||
| `jrshikoku://open/traininfo` | 遅延速報EX |
|
||||
| `jrshikoku://open/operation` | 運行情報 |
|
||||
| `jrshikoku://open/settings` | 設定 |
|
||||
| `jrshikoku://open/topmenu` | トップメニュー |
|
||||
| `jrshikoku://positions/apps` | 走行位置 |
|
||||
|
||||
URLの処理は `App.tsx` の `routeFromUrl` で実装されています。
|
||||
|
||||
### Androidウィジェット
|
||||
|
||||
ホーム画面に配置可能なウィジェットからアプリを起動できます。
|
||||
|
||||
| ウィジェット名 | 説明 |
|
||||
|---|---|
|
||||
| `JR_shikoku_train_info` | 遅延速報EX |
|
||||
| `JR_shikoku_apps_shortcut` | クイックアクセス(各機能へのショートカットタイル) |
|
||||
| `JR_shikoku_felica_balance` | ICカード残高表示 |
|
||||
| `JR_shikoku_info` | 運行情報 |
|
||||
| `JR_shikoku_train_strange` | 怪レい列車 |
|
||||
|
||||
ウィジェットのタップ時は `jrshikoku://` スキームでアプリ内画面へ遷移します。
|
||||
実装: `components/AndroidWidget/widget-task-handler.tsx`
|
||||
|
||||
### iOSウィジェット(WidgetKit)
|
||||
|
||||
WidgetKit拡張としてホーム画面ウィジェットを提供しています。Live Activities にも対応しています。
|
||||
設定: `targets/widget/Info.plist`
|
||||
|
||||
### プッシュ通知
|
||||
|
||||
通知タップ時に対応する画面へ遷移します。
|
||||
|
||||
| アクション | 遷移先 |
|
||||
|---|---|
|
||||
| `delay-ex` | 遅延速報EX |
|
||||
| `strange-train` | 走行位置(怪レい列車) |
|
||||
| `information` | 運行情報 |
|
||||
|
||||
実装: `stateBox/useNotifications.tsx`
|
||||
|
||||
### NFC(FeliCa)
|
||||
|
||||
NFC-Fを利用した交通系ICカードの読み取りに対応しています(Android)。アプリ内からスキャンを開始する形式です。
|
||||
実装: `modules/expo-felica-reader/`
|
||||
|
||||
## <20>📱 ビルド
|
||||
|
||||
```bash
|
||||
# APKビルド(Android)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { FC } from "react";
|
||||
import { Platform, StatusBar, View } from "react-native";
|
||||
import { Platform, StatusBar } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
|
||||
const StatusbarDetect: FC = () => {
|
||||
if (Platform.OS == "ios") {
|
||||
return <StatusBar barStyle="dark-content" />;
|
||||
} else if (Platform.OS == "android") {
|
||||
return <View />;
|
||||
}
|
||||
const { isDark } = useThemeColors();
|
||||
const barStyle = isDark ? "light-content" : "dark-content";
|
||||
return <StatusBar barStyle={barStyle} translucent backgroundColor="transparent" />;
|
||||
};
|
||||
|
||||
export default StatusbarDetect;
|
||||
|
||||
24
Top.tsx
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useColorScheme } from "react-native";
|
||||
import Apps from "./components/Apps";
|
||||
import TrainBase from "./components/trainbaseview";
|
||||
import HowTo from "./howto";
|
||||
@@ -18,10 +19,16 @@ import { StationDiagramView } from "@/components/StationDiagram/StationDiagramVi
|
||||
const Stack = createStackNavigator();
|
||||
export const Top = () => {
|
||||
const { webview } = useCurrentTrain();
|
||||
const { navigate, addListener, isFocused } = useNavigation();
|
||||
const { navigate, addListener, isFocused } = useNavigation<any>();
|
||||
const isDark = useColorScheme() === "dark";
|
||||
const bgColor = isDark ? "#1c1c1e" : "#ffffff";
|
||||
|
||||
//地図用
|
||||
const { mapSwitch } = useTrainMenu();
|
||||
const mapSwitchRef = useRef(mapSwitch);
|
||||
useEffect(() => {
|
||||
mapSwitchRef.current = mapSwitch;
|
||||
}, [mapSwitch]);
|
||||
|
||||
const goToFavoriteList = () =>
|
||||
navigate("positions", { screen: "favoriteList" });
|
||||
@@ -31,26 +38,29 @@ export const Top = () => {
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
const goToTrainMenu = () => {
|
||||
const goToTrainMenu = useCallback(() => {
|
||||
if (Platform.OS === "web") {
|
||||
Linking.openURL("https://train.jr-shikoku.co.jp/");
|
||||
setTimeout(() => navigate("topMenu", { screen: "menu" }), 100);
|
||||
return;
|
||||
}
|
||||
if (!isFocused()) navigate("positions", { screen: "Apps" });
|
||||
else if (mapSwitch == "true")
|
||||
else if (mapSwitchRef.current == "true")
|
||||
navigate("positions", { screen: "trainMenu" });
|
||||
else webview.current?.injectJavaScript(`AccordionClassEvent()`);
|
||||
return;
|
||||
};
|
||||
}, [isFocused, navigate, webview]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = addListener("tabPress", goToTrainMenu);
|
||||
return unsubscribe;
|
||||
}, [addListener, mapSwitch]);
|
||||
}, [addListener, goToTrainMenu]);
|
||||
|
||||
return (
|
||||
<Stack.Navigator id={null} detachInactiveScreens={false}>
|
||||
<Stack.Navigator
|
||||
id={null}
|
||||
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Apps"
|
||||
options={{
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Platform, ToastAndroid } from "react-native";
|
||||
import * as Updates from "expo-updates";
|
||||
|
||||
export const UpdateAsync = () => {
|
||||
if (__DEV__) return; // dev client では expo-updates は無効
|
||||
Updates.checkForUpdateAsync()
|
||||
.then((update) => {
|
||||
if (!update.isAvailable) return;
|
||||
@@ -16,7 +17,7 @@ export const UpdateAsync = () => {
|
||||
50
|
||||
);
|
||||
}
|
||||
Updates.fetchUpdateAsync().then(Updates.reloadAsync);
|
||||
Updates.fetchUpdateAsync().then(() => Updates.reloadAsync());
|
||||
return;
|
||||
})
|
||||
.catch((e) => {
|
||||
|
||||
564
app.json
@@ -2,12 +2,14 @@
|
||||
"expo": {
|
||||
"name": "JR四国非公式",
|
||||
"slug": "jrshikoku",
|
||||
"scheme": "jrshikoku",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android",
|
||||
"web"
|
||||
],
|
||||
"version": "6.0.4",
|
||||
"version": "7.0.0",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"orientation": "default",
|
||||
"icon": "./assets/icons/s8600.png",
|
||||
"splash": {
|
||||
@@ -22,9 +24,10 @@
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"buildNumber": "50",
|
||||
"supportsTablet": false,
|
||||
"buildNumber": "61",
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "jrshikokuinfo.xprocess.hrkn",
|
||||
"appleTeamId": "54CRDT797G",
|
||||
"config": {
|
||||
"googleMapsApiKey": "AIzaSyAVGDTjBkR_0wkQiNkoo5WDLhqXCjrjk8Y"
|
||||
},
|
||||
@@ -34,20 +37,57 @@
|
||||
"0003",
|
||||
"FE00"
|
||||
],
|
||||
"ITSAppUsesNonExemptEncryption": false
|
||||
"ITSAppUsesNonExemptEncryption": false,
|
||||
"NSSupportsLiveActivities": true,
|
||||
"NSSupportsLiveActivitiesFrequentUpdates": true,
|
||||
"UIBackgroundModes": [
|
||||
"audio"
|
||||
]
|
||||
},
|
||||
"entitlements": {
|
||||
"com.apple.developer.nfc.readersession.formats": [
|
||||
"TAG"
|
||||
],
|
||||
"com.apple.security.application-groups": [
|
||||
"group.jrshikokuinfo.xprocess.hrkn"
|
||||
]
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"package": "jrshikokuinfo.xprocess.hrkn",
|
||||
"versionCode": 28,
|
||||
"versionCode": 30,
|
||||
"intentFilters": [
|
||||
{
|
||||
"action": "VIEW",
|
||||
"data": [
|
||||
{
|
||||
"scheme": "jrshikoku"
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"BROWSABLE",
|
||||
"DEFAULT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "VIEW",
|
||||
"data": [
|
||||
{
|
||||
"scheme": "jrshikoku",
|
||||
"host": "open",
|
||||
"pathPrefix": "/felica"
|
||||
}
|
||||
],
|
||||
"category": [
|
||||
"BROWSABLE",
|
||||
"DEFAULT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"ACCESS_FINE_LOCATION",
|
||||
"NFC",
|
||||
"POST_NOTIFICATIONS",
|
||||
"android.permission.ACCESS_COARSE_LOCATION",
|
||||
"android.permission.ACCESS_FINE_LOCATION"
|
||||
],
|
||||
@@ -67,7 +107,20 @@
|
||||
"policy": "sdkVersion"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-font",
|
||||
"./plugins/with-android-local-properties",
|
||||
"./plugins/with-nfc-widget-guard",
|
||||
"@bacons/apple-targets",
|
||||
[
|
||||
"expo-font",
|
||||
{
|
||||
"fonts": [
|
||||
"./assets/fonts/jr-nishi.otf",
|
||||
"./assets/fonts/DelaGothicOne-Regular.ttf",
|
||||
"./assets/fonts/JNRfont_pict.ttf",
|
||||
"./assets/fonts/DiaPro-Regular.otf"
|
||||
]
|
||||
}
|
||||
],
|
||||
"expo-localization",
|
||||
[
|
||||
"expo-screen-orientation",
|
||||
@@ -91,17 +144,7 @@
|
||||
"minWidth": "70dp",
|
||||
"minHeight": "50dp",
|
||||
"description": "JR四国列車遅延速報EXのウィジェットです。30分ごとに自動更新します。タッチすると強制更新します。",
|
||||
"previewImage": "./assets/icon.png",
|
||||
"updatePeriodMillis": 1800000,
|
||||
"resizeMode": "horizontal|vertical"
|
||||
},
|
||||
{
|
||||
"name": "JR_shikoku_train_strange",
|
||||
"label": "怪レい列車",
|
||||
"minWidth": "70dp",
|
||||
"minHeight": "50dp",
|
||||
"description": "JR四国怪レい列車BOTのウィジェットです。30分ごとに自動更新します。タッチすると強制更新します。",
|
||||
"previewImage": "./assets/icon.png",
|
||||
"previewImage": "./assets/widgetResource/JR_shikoku_train_info.png",
|
||||
"updatePeriodMillis": 1800000,
|
||||
"resizeMode": "horizontal|vertical"
|
||||
},
|
||||
@@ -111,7 +154,7 @@
|
||||
"minWidth": "70dp",
|
||||
"minHeight": "50dp",
|
||||
"description": "JR四国運行情報のウィジェットです。30分ごとに自動更新します。タッチすると強制更新します。",
|
||||
"previewImage": "./assets/icon.png",
|
||||
"previewImage": "./assets/widgetResource/JR_shikoku_info.png",
|
||||
"updatePeriodMillis": 1800000,
|
||||
"resizeMode": "horizontal|vertical"
|
||||
},
|
||||
@@ -121,7 +164,17 @@
|
||||
"minWidth": "70dp",
|
||||
"minHeight": "50dp",
|
||||
"description": "JR四国非公式アプリの各種リンクを表示するウィジェットです。",
|
||||
"previewImage": "./assets/icon.png",
|
||||
"previewImage": "./assets/widgetResource/JR_shikoku_apps_shortcut.png",
|
||||
"updatePeriodMillis": 1800000,
|
||||
"resizeMode": "horizontal|vertical"
|
||||
},
|
||||
{
|
||||
"name": "JR_shikoku_felica_balance",
|
||||
"label": "ICカード残高",
|
||||
"minWidth": "70dp",
|
||||
"minHeight": "50dp",
|
||||
"description": "Felica対応ICカードの残高をホーム画面に表示するウィジェットです。タップでスキャン画面を開きます。",
|
||||
"previewImage": "./assets/widgetResource/JR_shikoku_felica_balance.png",
|
||||
"updatePeriodMillis": 1800000,
|
||||
"resizeMode": "horizontal|vertical"
|
||||
}
|
||||
@@ -450,9 +503,480 @@
|
||||
"foregroundImage": "./assets/icons/ef210.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32ns",
|
||||
"ios": "./assets/icons/s32ns.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32ns.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32s",
|
||||
"ios": "./assets/icons/s32s.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32s.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32kpuy1",
|
||||
"ios": "./assets/icons/s32kpuy1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32kpuy1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32kpuy2",
|
||||
"ios": "./assets/icons/s32kpuy2.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32kpuy2.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32thtk",
|
||||
"ios": "./assets/icons/s32thtk.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32thtk.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32oni1",
|
||||
"ios": "./assets/icons/s32oni1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32oni1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32oni1k",
|
||||
"ios": "./assets/icons/s32oni1k.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32oni1k.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32to4",
|
||||
"ios": "./assets/icons/s32to4.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32to4.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "32toai",
|
||||
"ios": "./assets/icons/s32to_ai.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32to_ai.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "54s",
|
||||
"ios": "./assets/icons/s54s.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s54s.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "54to0ys",
|
||||
"ios": "./assets/icons/s54to0ys.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s54to0ys.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "54nany1",
|
||||
"ios": "./assets/icons/s54nany1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s54nany1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "54nany2",
|
||||
"ios": "./assets/icons/s54nany2.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s54nany2.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "54smek1",
|
||||
"ios": "./assets/icons/s32smek1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s32smek1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "40s",
|
||||
"ios": "./assets/icons/s40.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s40.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "40w",
|
||||
"ios": "./assets/icons/w40.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/w40.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185cm",
|
||||
"ios": "./assets/icons/s185cm.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185cm.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185g",
|
||||
"ios": "./assets/icons/s185g.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185g.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185tu_uzu",
|
||||
"ios": "./assets/icons/s185tu_uzu.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185tu_uzu.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185ap1",
|
||||
"ios": "./assets/icons/s185ap1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185ap1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185mm2",
|
||||
"ios": "./assets/icons/s185mm2.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185mm2.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "185ym2",
|
||||
"ios": "./assets/icons/s185ym2.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s185ym2.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1200",
|
||||
"ios": "./assets/icons/s1200.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s1200.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1201",
|
||||
"ios": "./assets/icons/s1201.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s1201.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1501",
|
||||
"ios": "./assets/icons/s1501.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s1501.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1550",
|
||||
"ios": "./assets/icons/s1550.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s1550.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1551",
|
||||
"ios": "./assets/icons/s1551.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s1551.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2000uwa",
|
||||
"ios": "./assets/icons/s2000_uwa.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2000_uwa.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2000nl",
|
||||
"ios": "./assets/icons/s2000nl.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2000nl.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2000-3",
|
||||
"ios": "./assets/icons/s2000-3.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2000-3.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2000ganp1",
|
||||
"ios": "./assets/icons/s2000ganp1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2000ganp1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2600apr",
|
||||
"ios": "./assets/icons/s2600apr.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2600apr.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2600apb",
|
||||
"ios": "./assets/icons/s2600apb.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2600apb.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2700asi",
|
||||
"ios": "./assets/icons/s2700_asi.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2700_asi.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2700smn",
|
||||
"ios": "./assets/icons/s2700_smn.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2700_smn.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2700uzu",
|
||||
"ios": "./assets/icons/s2700_uzu.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2700_uzu.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2700apy1",
|
||||
"ios": "./assets/icons/s2700apy1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2700apy1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2700apr1",
|
||||
"ios": "./assets/icons/s2700apr1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s2700apr1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3600",
|
||||
"ios": "./assets/icons/s3600.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s3600.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6000f",
|
||||
"ios": "./assets/icons/s6000f.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s6000f.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7001",
|
||||
"ios": "./assets/icons/s7001.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s7001.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8001nr",
|
||||
"ios": "./assets/icons/s8001nr.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s8001nr.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9000",
|
||||
"ios": "./assets/icons/s9000.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s9000.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640ht",
|
||||
"ios": "./assets/icons/tosa9640ht.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640ht.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640mo1",
|
||||
"ios": "./assets/icons/tosa9640mo1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640mo1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640mo2",
|
||||
"ios": "./assets/icons/tosa9640mo2.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640mo2.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640tyg",
|
||||
"ios": "./assets/icons/tosa9640tyg.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640tyg.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640tyb",
|
||||
"ios": "./assets/icons/tosa9640tyb.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640tyb.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9640jbl",
|
||||
"ios": "./assets/icons/tosa9640jbl.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/tosa9640jbl.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "W741",
|
||||
"ios": "./assets/icons/w741.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/w741.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EF210a",
|
||||
"ios": "./assets/icons/ef210a.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/ef210a.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EF210n",
|
||||
"ios": "./assets/icons/ef210n.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/ef210n.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EF210n1",
|
||||
"ios": "./assets/icons/ef210n1.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/ef210n1.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EF210l3",
|
||||
"ios": "./assets/icons/ef210l3.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/ef210l3.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "T45000",
|
||||
"ios": "./assets/icons/s150001to.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/s150001to.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Wk141",
|
||||
"ios": "./assets/icons/w141jg.png",
|
||||
"android": {
|
||||
"foregroundImage": "./assets/icons/w141jg.png",
|
||||
"backgroundColor": "#001413"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"kotlinVersion": "2.1.20"
|
||||
},
|
||||
"ios": {
|
||||
"deploymentTarget": "16.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"expo-audio",
|
||||
"expo-video",
|
||||
"expo-web-browser",
|
||||
"expo-asset",
|
||||
"expo-sharing",
|
||||
[
|
||||
"react-native-maps",
|
||||
{
|
||||
"iosGoogleMapsApiKey": "AIzaSyAVGDTjBkR_0wkQiNkoo5WDLhqXCjrjk8Y",
|
||||
"androidGoogleMapsApiKey": "AIzaSyAmFb-Yj033bXZWlSzNrfq_0jc1PgRrWcE"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
assets/icons/ef210a.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
assets/icons/ef210l3.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
assets/icons/ef210n.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
assets/icons/ef210n1.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
@@ -1,45 +1,101 @@
|
||||
export default () =>{
|
||||
return [
|
||||
{ "id": "32", "name": "キハ32形", "icon": require("./32.png") },
|
||||
{ "id": "32kpuuy", "name": "キハ32形かっぱうようよ号", "icon": require("./32kpuuy.png") },
|
||||
//{ "id": "32", "name": "キハ32形", "icon": require("./32.png") },
|
||||
{ "id": "32ns", "name": "キハ32形(標準色)", "icon": require("./s32ns.png") },
|
||||
{ "id": "32s", "name": "キハ32形(新塗装)", "icon": require("./s32s.png") },
|
||||
//{ "id": "32kpuuy", "name": "キハ32形かっぱうようよ号", "icon": require("./32kpuuy.png") },
|
||||
{ "id": "32kpuy1", "name": "キハ32形かっぱうようよ号①", "icon": require("./s32kpuy1.png") },
|
||||
{ "id": "32kpuy2", "name": "キハ32形かっぱうようよ号②", "icon": require("./s32kpuy2.png") },
|
||||
{ "id": "32tht", "name": "キハ32形新幹線ホビートレイン", "icon": require("./32tht.png") },
|
||||
{ "id": "32thtk", "name": "キクハ32形新幹線ホビートレイン", "icon": require("./s32thtk.png") },
|
||||
{ "id": "32oni1", "name": "キハ32形特別塗装①", "icon": require("./s32oni1.png") },
|
||||
{ "id": "32oni1k", "name": "キハ32形特別塗装①(連結)", "icon": require("./s32oni1k.png") },
|
||||
//{ "id": "32to4", "name": "キハ32形(土讃色)", "icon": require("./s32to4.png") },
|
||||
{ "id": "32toai", "name": "キクハ32形藍よしのがわトロッコ", "icon": require("./s32to_ai.png") },
|
||||
{ "id": "32at", "name": "キクハ32形アンパンマントロッコ", "icon": require("./32at.png") },
|
||||
{ "id": "54", "name": "キハ54形", "icon": require("./54.png") },
|
||||
//{ "id": "54", "name": "キハ54形", "icon": require("./54.png") },
|
||||
{ "id": "54s", "name": "キハ54形(四国色)", "icon": require("./s54s.png") },
|
||||
{ "id": "54st", "name": "キハ54形しまんトロッコ", "icon": require("./54st.png") },
|
||||
{ "id": "40", "name": "キハ40", "icon": require("./40.png") },
|
||||
//{ "id": "54to0ys", "name": "キハ54形(ゆとりすと)", "icon": require("./s54to0ys.png") },
|
||||
{ "id": "54nany1", "name": "キハ54形なんよ号①", "icon": require("./s54nany1.png") },
|
||||
{ "id": "54nany2", "name": "キハ54形なんよ号②", "icon": require("./s54nany2.png") },
|
||||
{ "id": "54smek1", "name": "キハ54形しまんトロッコ(連結)", "icon": require("./s32smek1.png") },
|
||||
//{ "id": "40", "name": "キハ40", "icon": require("./40.png") },
|
||||
{ "id": "40s", "name": "キハ40(四国色)", "icon": require("./s40.png") },
|
||||
{ "id": "40w", "name": "キハ40(JR西日本色)", "icon": require("./w40.png") },
|
||||
{ "id": "185mrt", "name": "キハ185系四国色", "icon": require("./s185_mrt.png") },
|
||||
{ "id": "185cm", "name": "キハ185系普通列車色", "icon": require("./s185cm.png") },
|
||||
{ "id": "185g", "name": "キハ185系(緑帯)", "icon": require("./s185g.png") },
|
||||
{ "id": "185tu", "name": "キハ185系剣山色", "icon": require("./s185tu.png") },
|
||||
{ "id": "185tu_uzu", "name": "キハ185系剣山色(うずしお)", "icon": require("./s185tu_uzu.png") },
|
||||
{ "id": "185iyor", "name": "キハ185系伊予灘ものがたり(赤)", "icon": require("./s185iyor.png") },
|
||||
{ "id": "185iyoy", "name": "キハ185系伊予灘ものがたり(黄)", "icon": require("./s185iyoy.png") },
|
||||
{ "id": "185toai", "name": "キハ185系藍よしのがわトロッコ", "icon": require("./s185to_ai.png") },
|
||||
{ "id": "185mm1", "name": "キハ185系四国まんなか千年ものがたり(緑)", "icon": require("./s185mm1.png") },
|
||||
{ "id": "185mm2", "name": "キハ185系四国まんなか千年ものがたり(赤)", "icon": require("./s185mm2.png") },
|
||||
{ "id": "185ym1", "name": "キハ185系時代の夜明けのものがたり(茶)", "icon": require("./s185ym1.png") },
|
||||
{ "id": "185ym2", "name": "キハ185系時代の夜明けのものがたり(緑)", "icon": require("./s185ym2.png") },
|
||||
{ "id": "185ap1", "name": "キハ185系アンパンマン", "icon": require("./s185ap1.png") },
|
||||
{ "id": "1000", "name": "1000形", "icon": require("./s1000.png") },
|
||||
{ "id": "1200n", "name": "1200形", "icon": require("./s1200n.png") },
|
||||
{ "id": "1500", "name": "1500形", "icon": require("./s1500.png") },
|
||||
{ "id": "5001", "name": "5000系(二階建て)", "icon": require("./s5001.png") },
|
||||
{ "id": "5001k", "name": "5000系(平屋側)", "icon": require("./s5001k.png") },
|
||||
{ "id": "6000p", "name": "6000系", "icon": require("./s6000p.png") },
|
||||
{ "id": "7000", "name": "7000系", "icon": require("./s7000.png") },
|
||||
{ "id": "7200", "name": "7200系", "icon": require("./s7200.png") },
|
||||
//{ "id": "1200n", "name": "1200形", "icon": require("./s1200n.png") },
|
||||
{ "id": "1200", "name": "1200形(旧塗装)", "icon": require("./s1200.png") },
|
||||
{ "id": "1201", "name": "1201形", "icon": require("./s1201.png") },
|
||||
//{ "id": "1500", "name": "1500形", "icon": require("./s1500.png") },
|
||||
{ "id": "1501", "name": "1500形 1501", "icon": require("./s1501.png") },
|
||||
{ "id": "1550", "name": "1500形 1550", "icon": require("./s1550.png") },
|
||||
{ "id": "1551", "name": "1500形 1551", "icon": require("./s1551.png") },
|
||||
{ "id": "2000asi", "name": "2000系", "icon": require("./s2000_asi.png") },
|
||||
{ "id": "2000uwa", "name": "2000系(宇和海色)", "icon": require("./s2000_uwa.png") },
|
||||
{ "id": "N2000", "name": "N2000系", "icon": require("./s2000n.png") },
|
||||
{ "id": "2000nl", "name": "N2000系(南風色)", "icon": require("./s2000nl.png") },
|
||||
{ "id": "2000-3", "name": "N2000系(試作)", "icon": require("./s2000-3.png") },
|
||||
{ "id": "2000ganp1", "name": "2000系(復活塗装)", "icon": require("./s2000ganp1.png") },
|
||||
{ "id": "2002a", "name": "2000系アンパンマン", "icon": require("./s2002a.png") },
|
||||
{ "id": "2600", "name": "2600系" , "icon": require("./s2600.png")},
|
||||
{ "id": "2600apr", "name": "2600系アンパンマン(赤)", "icon": require("./s2600apr.png") },
|
||||
{ "id": "2600apb", "name": "2600系アンパンマン(青)", "icon": require("./s2600apb.png") },
|
||||
{ "id": "2700", "name": "2700系", "icon": require("./s2700.png") },
|
||||
{ "id": "2700asi", "name": "2700系(あしずり色)", "icon": require("./s2700_asi.png") },
|
||||
{ "id": "2700smn", "name": "2700系(しまんと色)", "icon": require("./s2700_smn.png") },
|
||||
{ "id": "2700uzu", "name": "2700系(うずしお色)", "icon": require("./s2700_uzu.png") },
|
||||
{ "id": "2700apy", "name": "2700系アンパンマン(黄)", "icon": require("./s2700apy.png") },
|
||||
{ "id": "2700apr", "name": "2700系アンパンマン(赤)", "icon": require("./s2700apr.png") },
|
||||
{ "id": "2700apy1", "name": "2700系アンパンマン(黄)①", "icon": require("./s2700apy1.png") },
|
||||
{ "id": "2700apr1", "name": "2700系アンパンマン(赤)①", "icon": require("./s2700apr1.png") },
|
||||
{ "id": "3600", "name": "3600系", "icon": require("./s3600.png") },
|
||||
{ "id": "5001", "name": "5000系(二階建て)", "icon": require("./s5001.png") },
|
||||
{ "id": "5001k", "name": "5000系(平屋側)", "icon": require("./s5001k.png") },
|
||||
//{ "id": "6000p", "name": "6000系", "icon": require("./s6000p.png") },
|
||||
{ "id": "6000f", "name": "6000系(Fライナー)", "icon": require("./s6000f.png") },
|
||||
{ "id": "7000", "name": "7000系", "icon": require("./s7000.png") },
|
||||
//{ "id": "7001", "name": "7000系 7001", "icon": require("./s7001.png") },
|
||||
{ "id": "7200", "name": "7200系", "icon": require("./s7200.png") },
|
||||
{ "id": "8000no", "name": "8000系(オレンジ)", "icon": require("./s8000no.png") },
|
||||
{ "id": "8000nr", "name": "8000系(赤)", "icon": require("./s8000nr.png") },
|
||||
{ "id": "8001nr", "name": "8000系(赤) 8001", "icon": require("./s8001nr.png") },
|
||||
{ "id": "8000ap", "name": "8000系アンパンマン", "icon": require("./s8000ap.png") },
|
||||
{ "id": "8000nn", "name": "8000系リニューアル改", "icon": require("./s8000nn.png") },
|
||||
{ "id": "8600", "name": "8600系", "icon": require("./s8600.png") },
|
||||
{ "id": "9000", "name": "9000系", "icon": require("./s9000.png") },
|
||||
{ "id": "9640", "name": "9640形(白)", "icon": require("./tosa9640.png") },
|
||||
{ "id": "9640ht", "name": "9640形(赤帯)", "icon": require("./tosa9640ht.png") },
|
||||
{ "id": "9640mo1", "name": "9640形もみじ号①", "icon": require("./tosa9640mo1.png") },
|
||||
{ "id": "9640mo2", "name": "9640形もみじ号②", "icon": require("./tosa9640mo2.png") },
|
||||
{ "id": "9640tyg", "name": "9640形(緑)", "icon": require("./tosa9640tyg.png") },
|
||||
{ "id": "9640tyb", "name": "9640形(青)", "icon": require("./tosa9640tyb.png") },
|
||||
{ "id": "9640jgr", "name": "9640形オープンデッキ(緑)", "icon": require("./tosa9640jgr.png") },
|
||||
{ "id": "9640jbl", "name": "9640形オープンデッキ(青)", "icon": require("./tosa9640jbl.png") },
|
||||
{ "id": "285", "name": "285系サンライズ瀬戸", "icon": require("./w285.png") },
|
||||
{ "id": "213w", "name": "ラ・マル・ド・ボァ", "icon": require("./w213w.png") },
|
||||
{ "id": "W741", "name": "DEC741系", "icon": require("./w741.png") },
|
||||
{ "id": "Wk141", "name": "キヤ141系", "icon": require("./w141jg.png") },
|
||||
{ "id": "EF65", "name": "EF65", "icon": require("./ef65.png") },
|
||||
{ "id": "EF210", "name": "EF210", "icon": require("./ef210.png") },
|
||||
//{ "id": "EF210", "name": "EF210", "icon": require("./ef210.png") },
|
||||
{ "id": "EF210a", "name": "EF210(300番代)", "icon": require("./ef210a.png") },
|
||||
{ "id": "EF210n", "name": "EF210(新塗装)", "icon": require("./ef210n.png") },
|
||||
{ "id": "EF210n1", "name": "EF210(新塗装①)", "icon": require("./ef210n1.png") },
|
||||
{ "id": "EF210l3", "name": "EF210(L3編成)", "icon": require("./ef210l3.png") },
|
||||
{ "id": "T45000", "name": "トラ45000", "icon": require("./s150001to.png") },
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BIN
assets/icons/s1200.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/icons/s1201.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
assets/icons/s150001to.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
assets/icons/s1501.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
assets/icons/s1550.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
assets/icons/s1551.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
assets/icons/s185ap1.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
assets/icons/s185cm.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
assets/icons/s185g.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
assets/icons/s185mm2.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
assets/icons/s185tu_uzu.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
assets/icons/s185ym2.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
assets/icons/s2000-3.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
assets/icons/s2000_uwa.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
assets/icons/s2000ganp1.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/icons/s2000nl.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
assets/icons/s2600apb.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
assets/icons/s2600apr.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
assets/icons/s2700_asi.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
assets/icons/s2700_smn.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
assets/icons/s2700_uzu.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
assets/icons/s2700apr1.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
assets/icons/s2700apy1.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/icons/s32kpuy1.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/icons/s32kpuy2.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
assets/icons/s32ns.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
assets/icons/s32oni1.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
assets/icons/s32oni1k.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
assets/icons/s32s.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/icons/s32smek1.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
assets/icons/s32thtk.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
assets/icons/s32to4.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
assets/icons/s32to_ai.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
assets/icons/s3600.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
assets/icons/s40.png
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
assets/icons/s54nany1.png
Normal file
|
After Width: | Height: | Size: 235 KiB |
BIN
assets/icons/s54nany2.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
assets/icons/s54s.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
assets/icons/s54to0ys.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
assets/icons/s6000f.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
assets/icons/s7001.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
assets/icons/s8001nr.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
assets/icons/s9000.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
assets/icons/tosa9640ht.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
assets/icons/tosa9640jbl.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
assets/icons/tosa9640mo1.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/icons/tosa9640mo2.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
assets/icons/tosa9640tyb.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
assets/icons/tosa9640tyg.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
assets/icons/w141jg.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
assets/icons/w40.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
assets/icons/w741.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
21
assets/originData/spots.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 観光スポットデータ
|
||||
* StationNumber は "SP" プレフィックスで管理(路線とは別系統)
|
||||
* Station_JP の先頭ドット(.)はダイヤデータのキーと一致させるための命名規則
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
Station_JP: ".与島",
|
||||
Station_EN: "Yoshima",
|
||||
MyStation: "0",
|
||||
StationNumber: null,
|
||||
DispNum: "3",
|
||||
StationTimeTable: "",
|
||||
StationMap: "https://www.google.co.jp/maps/place/34.389472,133.816444",
|
||||
JrHpUrl: "https://www.jb-honshi.co.jp/yoshimapa/",
|
||||
jslodApi: "spot",
|
||||
lat: 34.389472,
|
||||
lng: 133.816444,
|
||||
isSpot: true,
|
||||
},
|
||||
];
|
||||
BIN
assets/relationLogo/elesite_logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/relationLogo/unyohub_logo.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
assets/sound/delay-support.wav
Normal file
BIN
assets/sound/rikka-test.mp3
Normal file
BIN
assets/widgetResource/JR_shikoku_apps_shortcut.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
assets/widgetResource/JR_shikoku_felica_balance.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/widgetResource/JR_shikoku_info.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/widgetResource/JR_shikoku_train_info.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -1,6 +1,15 @@
|
||||
module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
presets: [
|
||||
['babel-preset-expo', {
|
||||
'react-compiler': { enabled: false },
|
||||
lazyImports: true,
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
'./plugins/babel-plugin-disable-font-scaling',
|
||||
'react-native-reanimated/plugin',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,16 +2,19 @@ import React, { useRef } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import { EachTrainInfoCore } from "./EachTrainInfoCore";
|
||||
import { useSheetMaxHeight } from "./useSheetMaxHeight";
|
||||
export const EachTrainInfo = ({ payload }) => {
|
||||
if (!payload) return <></>;
|
||||
const actionSheetRef = useRef(null);
|
||||
const maxHeight = useSheetMaxHeight();
|
||||
if (!payload) return <></>;
|
||||
return (
|
||||
<ActionSheet
|
||||
gestureEnabled={true}
|
||||
CustomHeaderComponent={<></>}
|
||||
ref={actionSheetRef}
|
||||
drawUnderStatusBar={false}
|
||||
isModal={Platform.OS == "ios"}
|
||||
isModal={Platform.OS === "ios" && !Platform.isPad}
|
||||
containerStyle={{ maxHeight }}
|
||||
|
||||
//useBottomSafeAreaPadding={Platform.OS == "android"}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { FC } from "react";
|
||||
import { View, Text, TouchableWithoutFeedback, Alert } from "react-native";
|
||||
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
export const DataConnectedButton: FC<{
|
||||
i: string;
|
||||
@@ -8,6 +10,8 @@ export const DataConnectedButton: FC<{
|
||||
}> = ({ i, openTrainInfo }) => {
|
||||
const [station, se, time] = i.split(",");
|
||||
const { keyList } = useAllTrainDiagram();
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale, moderateScale } = useResponsive();
|
||||
// 列番が有効かどうかをチェックする関数
|
||||
const isValidTrainNumber = (trainNum: string): boolean => {
|
||||
return keyList.includes(trainNum);
|
||||
@@ -30,31 +34,31 @@ export const DataConnectedButton: FC<{
|
||||
}}
|
||||
key={station + time}
|
||||
>
|
||||
<View style={{ flexDirection: "row", backgroundColor: "#f5f5f5" }}>
|
||||
<View style={{ flexDirection: "row", backgroundColor: colors.backgroundTertiary }}>
|
||||
<View
|
||||
style={{
|
||||
padding: 8,
|
||||
flexDirection: "row",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
borderBottomColor: colors.borderLight,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 35,
|
||||
width: moderateScale(35),
|
||||
position: "relative",
|
||||
marginHorizontal: 15,
|
||||
flexDirection: "row",
|
||||
height: "10%",
|
||||
}}
|
||||
/>
|
||||
<Text style={{ fontSize:16, fontFamily: "DiaPro" }}>
|
||||
<Text style={{ fontSize: fontScale(16), fontFamily: "DiaPro", color: colors.text }}>
|
||||
{se === "増" ? "⬐" : "↳"}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 20, color: "#0000EE" }}>{time}</Text>
|
||||
<Text style={{ fontSize: fontScale(20), color: colors.textLink }}>{time}</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text style={{ fontSize: 18, width: 50 }}>
|
||||
<Text style={{ fontSize: fontScale(18), width: moderateScale(50), color: colors.text }}>
|
||||
{se === "増" ? "増結" : "解結"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
parseSeString,
|
||||
} from "@/utils/seUtils";
|
||||
import type { SeTypes } from "@/types";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
type seTypes =
|
||||
| "発編"
|
||||
@@ -63,6 +65,8 @@ export const EachStopList: FC<props> = ({
|
||||
array,
|
||||
isNotService = false,
|
||||
}) => {
|
||||
const { colors: themeColors, isDark } = useThemeColors();
|
||||
const { fontScale, moderateScale } = useResponsive();
|
||||
const [station, se, time, platformNum] = i.split(",") as [
|
||||
string,
|
||||
seTypes,
|
||||
@@ -70,22 +74,22 @@ export const EachStopList: FC<props> = ({
|
||||
string
|
||||
]; // 阿波池田,発,6:21,1
|
||||
let beforeSameStationData = null;
|
||||
// 運休系でない通常の発のみ、前の着を統合する
|
||||
// 休編(非推奨)は発着が不明なため、次の発と統合する
|
||||
if ((se.includes("発") && !se.includes("休")) || se === "休編") {
|
||||
// 発(通常発・休発・休発編)の場合、前の着(通常着・休着・休着編)と統合する
|
||||
if (se.includes("発")) {
|
||||
if (index > 0) {
|
||||
const beforeData = array[index - 1].split(",") as [string, seTypes, string];
|
||||
if (beforeData[0] == station) {
|
||||
// 前が着(通常着でも休着でも)の場合は統合
|
||||
if (beforeData[0] == station && beforeData[1].includes("着")) {
|
||||
beforeSameStationData = beforeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
let afterSameStationData = null;
|
||||
// 運休系でない通常の着のみ、次の発と統合する
|
||||
// 運休着(休着、休着編)は独立して表示する必要がある
|
||||
if (se.includes("着") && !se.includes("休")) {
|
||||
// 着(通常着・休着・休着編)の場合、次の発(通常発・休発・休発編)と統合される(非表示)
|
||||
if (se.includes("着")) {
|
||||
const afterData = array[index + 1]?.split(",") as [string, seTypes, string];
|
||||
if (afterData && afterData[0] == station) {
|
||||
// 次が発(通常発でも休発でも)なら、この着を非表示にして次の発で両方表示
|
||||
if (afterData && afterData[0] == station && afterData[1].includes("発")) {
|
||||
afterSameStationData = afterData;
|
||||
return <></>;
|
||||
}
|
||||
@@ -135,7 +139,8 @@ export const EachStopList: FC<props> = ({
|
||||
isCommunity,
|
||||
isCanceled,
|
||||
isDelayed,
|
||||
isNotService
|
||||
isNotService,
|
||||
isDark
|
||||
);
|
||||
// 打ち消し線用の通常色(遅延していない時の色)
|
||||
const normalColors = getStopListColors(
|
||||
@@ -143,7 +148,8 @@ export const EachStopList: FC<props> = ({
|
||||
isCommunity,
|
||||
isCanceled,
|
||||
false,
|
||||
isNotService
|
||||
isNotService,
|
||||
isDark
|
||||
);
|
||||
|
||||
// beforeSameStationData用の色設定
|
||||
@@ -161,14 +167,16 @@ export const EachStopList: FC<props> = ({
|
||||
beforeIsCommunity,
|
||||
isCanceled,
|
||||
isDelayed,
|
||||
isNotService
|
||||
isNotService,
|
||||
isDark
|
||||
);
|
||||
const beforeNormalColors = getStopListColors(
|
||||
beforeIsThrough,
|
||||
beforeIsCommunity,
|
||||
isCanceled,
|
||||
false,
|
||||
isNotService
|
||||
isNotService,
|
||||
isDark
|
||||
);
|
||||
beforeTimeTextColor = beforeColors.timeText;
|
||||
beforeNormalTimeTextColor = beforeNormalColors.timeText;
|
||||
@@ -191,7 +199,7 @@ export const EachStopList: FC<props> = ({
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 35,
|
||||
width: moderateScale(35),
|
||||
position: "relative",
|
||||
marginHorizontal: 15,
|
||||
flexDirection: "row",
|
||||
@@ -216,7 +224,7 @@ export const EachStopList: FC<props> = ({
|
||||
<View style={{ position: "relative", height: 25 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontSize: fontScale(20),
|
||||
position: "absolute",
|
||||
textAlignVertical: "center",
|
||||
left: -15,
|
||||
@@ -233,7 +241,7 @@ export const EachStopList: FC<props> = ({
|
||||
padding: 8,
|
||||
flexDirection: "row",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
borderBottomColor: themeColors.borderLight,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
@@ -241,7 +249,7 @@ export const EachStopList: FC<props> = ({
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontSize: fontScale(20),
|
||||
color: colors.text,
|
||||
fontStyle: isCommunity ? "italic" : "normal",
|
||||
textAlignVertical: "center",
|
||||
@@ -302,20 +310,21 @@ const TimeText: FC<{
|
||||
normalTextColor,
|
||||
}) => {
|
||||
const [seString, seType] = parseSeString(se);
|
||||
const { fontScale, moderateScale } = useResponsive();
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", opacity: isBefore ? 0.5 : 1 }}>
|
||||
{!!currentTrainData?.delay &&
|
||||
currentTrainData?.delay != "入線" &&
|
||||
currentTrainData?.delay != 0 && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: isBefore ? 14 : 15,
|
||||
fontSize: fontScale(isBefore ? 14 : 15),
|
||||
marginVertical: isDouble ? -2 : 0,
|
||||
color: normalTextColor,
|
||||
width: 60,
|
||||
width: moderateScale(60),
|
||||
position: "absolute",
|
||||
right: isBefore ? 125 : 120,
|
||||
right: isBefore ? moderateScale(125) : moderateScale(120),
|
||||
textAlign: "right",
|
||||
textDecorationLine: "line-through",
|
||||
fontStyle: seType == "community" ? "italic" : "normal",
|
||||
@@ -335,8 +344,8 @@ const TimeText: FC<{
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: isBefore ? 14 : 18,
|
||||
width: 50,
|
||||
fontSize: fontScale(isBefore ? 14 : 18),
|
||||
width: moderateScale(50),
|
||||
color: textColor,
|
||||
marginVertical: isDouble ? -2 : 0,
|
||||
fontStyle: seType == "community" ? "italic" : "normal",
|
||||
@@ -349,6 +358,8 @@ const TimeText: FC<{
|
||||
};
|
||||
const StationNumbersBox: FC<{ stn: string; se: seTypes }> = (props) => {
|
||||
const { stn, se } = props;
|
||||
const { fixed } = useThemeColors();
|
||||
const { fontScale } = useResponsive();
|
||||
const lineColor = lineColorList[stn.charAt(0)];
|
||||
const hasThrew =
|
||||
se == "通過" ||
|
||||
@@ -366,9 +377,9 @@ const StationNumbersBox: FC<{ stn: string; se: seTypes }> = (props) => {
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text
|
||||
style={{
|
||||
color: "white",
|
||||
color: fixed.textOnPrimary,
|
||||
textAlign: "center",
|
||||
fontSize: 10,
|
||||
fontSize: fontScale(10),
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
@@ -393,6 +404,7 @@ type StationTimeBoxType = {
|
||||
|
||||
const StationTimeBox: FC<StationTimeBoxType> = (props) => {
|
||||
const { delay, textColor, seType, se, time, isDouble, isBefore } = props;
|
||||
const { fontScale, moderateScale } = useResponsive();
|
||||
const dates = dayjs()
|
||||
.set("hour", parseInt(time.split(":")[0]))
|
||||
.set("minute", parseInt(time.split(":")[1]))
|
||||
@@ -401,10 +413,10 @@ const StationTimeBox: FC<StationTimeBoxType> = (props) => {
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: isBefore ? 14 : 20,
|
||||
fontSize: fontScale(isBefore ? 14 : 20),
|
||||
marginVertical: isDouble ? -2 : 0,
|
||||
color: textColor,
|
||||
width: 60,
|
||||
width: moderateScale(60),
|
||||
fontStyle: seType == "community" ? "italic" : "normal",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import React, { FC, useCallback, useEffect } from "react";
|
||||
import { Platform, TouchableOpacity, Text, StyleSheet } from "react-native";
|
||||
import { useTrainFollowActivity } from "@/lib/useTrainFollowActivity";
|
||||
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
|
||||
import { trainDataType } from "@/lib/trainPositionTextArray";
|
||||
|
||||
type Props = {
|
||||
currentTrainData: trainDataType;
|
||||
currentPosition: string[] | undefined;
|
||||
};
|
||||
|
||||
export const LiveActivityButton: FC<Props> = ({
|
||||
currentTrainData,
|
||||
currentPosition,
|
||||
}) => {
|
||||
const liveActivity = useTrainFollowActivity();
|
||||
const { allTrainDiagram } = useAllTrainDiagram();
|
||||
|
||||
const onLine = !!currentPosition?.toString().length;
|
||||
|
||||
const handleLiveActivity = useCallback(async () => {
|
||||
if (liveActivity.status === "active") {
|
||||
await liveActivity.end();
|
||||
return;
|
||||
}
|
||||
if (!onLine) return;
|
||||
|
||||
const diagramStr: string =
|
||||
(allTrainDiagram as Record<string, string>)[currentTrainData?.num] ?? "";
|
||||
const stations = diagramStr
|
||||
.split("#")
|
||||
.filter(Boolean)
|
||||
.map((s) => s.split(",")[0]);
|
||||
const destination =
|
||||
stations.length > 0 ? stations[stations.length - 1] : "";
|
||||
|
||||
const curSt = currentPosition?.[0] ?? currentTrainData?.Pos ?? "";
|
||||
const curIdx = stations.indexOf(curSt);
|
||||
const nextSt =
|
||||
curIdx >= 0 && curIdx + 1 < stations.length
|
||||
? stations[curIdx + 1]
|
||||
: currentPosition?.[1] ?? "";
|
||||
|
||||
await liveActivity.start({
|
||||
trainNumber: currentTrainData?.num ?? "",
|
||||
lineName: currentTrainData?.Line ?? "",
|
||||
destination,
|
||||
currentStation: curSt,
|
||||
nextStation: nextSt,
|
||||
delayMinutes:
|
||||
typeof currentTrainData?.delay === "number"
|
||||
? currentTrainData.delay
|
||||
: 0,
|
||||
scheduledArrival: "",
|
||||
});
|
||||
}, [
|
||||
liveActivity,
|
||||
onLine,
|
||||
allTrainDiagram,
|
||||
currentTrainData,
|
||||
currentPosition,
|
||||
]);
|
||||
|
||||
// 列車位置が変わったら Live Activity を自動更新
|
||||
useEffect(() => {
|
||||
if (liveActivity.status !== "active") return;
|
||||
if (!currentTrainData?.Pos) return;
|
||||
|
||||
const diagramStr: string =
|
||||
(allTrainDiagram as Record<string, string>)[currentTrainData?.num] ?? "";
|
||||
const stations = diagramStr
|
||||
.split("#")
|
||||
.filter(Boolean)
|
||||
.map((s) => s.split(",")[0]);
|
||||
|
||||
const curSt = currentPosition?.[0] ?? currentTrainData.Pos;
|
||||
const curIdx = stations.indexOf(curSt);
|
||||
const nextSt =
|
||||
curIdx >= 0 && curIdx + 1 < stations.length
|
||||
? stations[curIdx + 1]
|
||||
: currentPosition?.[1] ?? "";
|
||||
|
||||
liveActivity.update({
|
||||
currentStation: curSt,
|
||||
nextStation: nextSt,
|
||||
delayMinutes:
|
||||
typeof currentTrainData.delay === "number"
|
||||
? currentTrainData.delay
|
||||
: 0,
|
||||
scheduledArrival: "",
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
currentTrainData?.Pos,
|
||||
currentTrainData?.delay,
|
||||
currentPosition?.toString(),
|
||||
]);
|
||||
|
||||
if (Platform.OS !== "ios" || !liveActivity.available || !onLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={handleLiveActivity}
|
||||
disabled={liveActivity.status === "loading"}
|
||||
style={[
|
||||
styles.liveActivityButton,
|
||||
liveActivity.status === "active" && styles.liveActivityButtonActive,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.liveActivityButtonText}>
|
||||
{liveActivity.status === "active"
|
||||
? "🔴 列車追従 Live Activity 終了"
|
||||
: liveActivity.status === "loading"
|
||||
? "⏳ 開始中..."
|
||||
: "🟢 列車追従 Live Activity 開始"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
liveActivityButton: {
|
||||
marginHorizontal: 10,
|
||||
marginVertical: 6,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 10,
|
||||
backgroundColor: "rgba(0, 153, 204, 0.12)",
|
||||
borderWidth: 1,
|
||||
borderColor: "rgba(0, 153, 204, 0.5)",
|
||||
alignItems: "center",
|
||||
},
|
||||
liveActivityButtonActive: {
|
||||
backgroundColor: "rgba(220, 50, 50, 0.1)",
|
||||
borderColor: "rgba(220, 50, 50, 0.5)",
|
||||
},
|
||||
liveActivityButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
color: "#0099cc",
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FC } from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { TrainDataView } from "./TrainDataView";
|
||||
import { trainDataType } from "@/lib/trainPositionTextArray";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
@@ -18,12 +18,13 @@ export const LongHeader:FC<props> = ({
|
||||
navigate,
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
}}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
>
|
||||
<TrainDataView
|
||||
currentTrainData={currentTrainData}
|
||||
@@ -33,6 +34,6 @@ export const LongHeader:FC<props> = ({
|
||||
navigate={navigate}
|
||||
key={"LongHeader"}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { trainPosition, trainDataType } from "@/lib/trainPositionTextArray";
|
||||
import React, { FC } from "react";
|
||||
import { View, Text, TextStyle, ViewStyle } from "react-native";
|
||||
import { View, Text, ViewStyle } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
type stateBox = {
|
||||
currentTrainData: trainDataType | undefined;
|
||||
@@ -21,6 +23,8 @@ export const PositionBox: FC<stateBox> = (props) => {
|
||||
platformDescription,
|
||||
lineNumber,
|
||||
} = props;
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useResponsive();
|
||||
let firstText = "";
|
||||
let secondText = "";
|
||||
let marginText = "";
|
||||
@@ -46,22 +50,22 @@ export const PositionBox: FC<stateBox> = (props) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View style={{ ...(mode == 2 ? boxStyle2 : boxStyle), ...style }}>
|
||||
<Text style={{ fontSize: 12, color: "#0099CC" }}>{title}</Text>
|
||||
<View style={{ ...(mode == 2 ? boxStyle2 : boxStyle), backgroundColor: colors.surface, ...style }}>
|
||||
<Text style={{ fontSize: fontScale(12), color: colors.textAccent }}>{title}</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
<View style={{ flexDirection: mode == 2 ? "row" : "column" }}>
|
||||
{firstText && (
|
||||
<Text style={mode == 2 ? boxTextStyle2 : (isBetween ? boxTextStyle : boxTextStyleBig)}>
|
||||
<Text style={[mode == 2 ? { fontSize: fontScale(18), textAlign: "right" as const } : (isBetween ? { fontSize: fontScale(25), textAlign: "right" as const } : { fontSize: fontScale(28), textAlign: "right" as const }), { color: colors.textAccent }]}>
|
||||
{firstText}
|
||||
</Text>
|
||||
)}
|
||||
{marginText && (
|
||||
<Text style={{ color: "#0099CC", textAlign: "right" }}>
|
||||
<Text style={{ color: colors.textAccent, textAlign: "right" }}>
|
||||
{marginText}
|
||||
</Text>
|
||||
)}
|
||||
{secondText && (
|
||||
<Text style={mode == 2 ? boxTextStyle2 :(isBetween ? boxTextStyle : boxTextStyleMini)}>
|
||||
<Text style={[mode == 2 ? { fontSize: fontScale(18), textAlign: "right" as const } :(isBetween ? { fontSize: fontScale(25), textAlign: "right" as const } : { fontSize: fontScale(16), textAlign: "right" as const }), { color: colors.textAccent }]}>
|
||||
{secondText}
|
||||
</Text>
|
||||
)}
|
||||
@@ -70,8 +74,9 @@ export const PositionBox: FC<stateBox> = (props) => {
|
||||
<View style={{ flexDirection: mode == 2 ? "row" : "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
...{ ...(mode == 2 ? boxTextStyle2 : boxTextStyle) },
|
||||
fontSize: 10,
|
||||
...(mode == 2 ? { fontSize: fontScale(18), textAlign: "right" as const } : { fontSize: fontScale(25), textAlign: "right" as const }),
|
||||
fontSize: fontScale(10),
|
||||
color: colors.textAccent,
|
||||
}}
|
||||
>
|
||||
{" " + externalText}
|
||||
@@ -84,39 +89,13 @@ export const PositionBox: FC<stateBox> = (props) => {
|
||||
};
|
||||
const boxStyle: ViewStyle = {
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 10,
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
};
|
||||
const boxStyle2: ViewStyle = {
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 10,
|
||||
padding: 5,
|
||||
margin: 5,
|
||||
};
|
||||
const boxTextStyle2: TextStyle = {
|
||||
fontSize: 18,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
const boxTextStyleBig: TextStyle = {
|
||||
fontSize: 28,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
|
||||
|
||||
const boxTextStyleMini: TextStyle = {
|
||||
fontSize: 16,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
|
||||
|
||||
const boxTextStyle: TextStyle = {
|
||||
fontSize: 25,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import React from "react";
|
||||
import { View, Text, LayoutAnimation, TouchableOpacity } from "react-native";
|
||||
import { View, Text, TouchableOpacity } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
export const ScrollStickyContent = (props) => {
|
||||
const { currentTrainData, showThrew, setShowThrew, haveThrough } = props;
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useResponsive();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignItems: "center",
|
||||
backgroundColor: "#ffffffc2",
|
||||
backgroundColor: colors.surface,
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
@@ -16,18 +20,18 @@ export const ScrollStickyContent = (props) => {
|
||||
padding: 8,
|
||||
flexDirection: "row",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#ffffffc2",
|
||||
borderBottomColor: colors.border,
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 20 }}>停車駅</Text>
|
||||
<Text style={{ fontSize: fontScale(20), color: colors.text }}>停車駅</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
{!isNaN(currentTrainData?.delay) && currentTrainData?.delay != 0 && (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 15,
|
||||
color: "black",
|
||||
fontSize: fontScale(15),
|
||||
color: colors.text,
|
||||
position: "absolute",
|
||||
right: 110,
|
||||
textAlign: "right",
|
||||
@@ -39,12 +43,12 @@ export const ScrollStickyContent = (props) => {
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontSize: fontScale(20),
|
||||
color: isNaN(currentTrainData?.delay)
|
||||
? "black"
|
||||
? colors.text
|
||||
: currentTrainData?.delay == 0
|
||||
? "black"
|
||||
: "red",
|
||||
? colors.text
|
||||
: colors.textError,
|
||||
width: 60,
|
||||
}}
|
||||
>
|
||||
@@ -53,23 +57,20 @@ export const ScrollStickyContent = (props) => {
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (!haveThrough) return;
|
||||
LayoutAnimation.configureNext({
|
||||
duration: 200,
|
||||
update: { type: "easeInEaseOut", springDamping: 0.6 },
|
||||
});
|
||||
setShowThrew(!showThrew);
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontSize: fontScale(12),
|
||||
width: 50,
|
||||
paddingBottom: 0,
|
||||
margin: "auto",
|
||||
textAlign: "center",
|
||||
textAlignVertical: "center",
|
||||
opacity: haveThrough ? 1 : 0,
|
||||
color: colors.text,
|
||||
}}
|
||||
>
|
||||
(通過{showThrew ? "▼" : "▶"})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FC } from "react";
|
||||
import { ScrollView } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { TrainDataView } from "./TrainDataView";
|
||||
import { trainDataType } from "@/lib/trainPositionTextArray";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
@@ -18,13 +18,13 @@ export const ShortHeader:FC<props> = ({
|
||||
navigate,
|
||||
}) => {
|
||||
return (
|
||||
<ScrollView
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flex: 1,
|
||||
width: "100%",
|
||||
alignSelf: "stretch",
|
||||
}}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
>
|
||||
<TrainDataView
|
||||
mode={2}
|
||||
@@ -35,6 +35,6 @@ export const ShortHeader:FC<props> = ({
|
||||
navigate={navigate}
|
||||
key={"ShortHeader"}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Text, TouchableOpacity } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
export const ShowSpecialTrain = ({
|
||||
isTrainDataNothing,
|
||||
setTrainData,
|
||||
trueTrainID,
|
||||
}) => {
|
||||
const { allTrainDiagram } = useAllTrainDiagram();
|
||||
const { colors } = useThemeColors();
|
||||
const replaceSpecialTrainDetail = (trainNum) => {
|
||||
let TD = allTrainDiagram[trainNum];
|
||||
if (!TD) return;
|
||||
@@ -22,7 +24,7 @@ export const ShowSpecialTrain = ({
|
||||
style={{
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
borderColor: "blue",
|
||||
borderColor: colors.textAccent,
|
||||
borderWidth: 1,
|
||||
margin: 10,
|
||||
borderRadius: 5,
|
||||
@@ -30,7 +32,7 @@ export const ShowSpecialTrain = ({
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ fontSize: 18, fontWeight: "bold", color: "black" }}
|
||||
style={{ fontSize: 18, fontWeight: "bold", color: colors.text }}
|
||||
>
|
||||
本来の列車情報候補を表示:({ids})
|
||||
</Text>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { CSSProperties, FC } from "react";
|
||||
import { View, Text, StyleProp, TextStyle, ViewStyle } from "react-native";
|
||||
import { View, Text, StyleProp, ViewStyle } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
type stateBox = {
|
||||
text: string;
|
||||
@@ -10,19 +12,22 @@ type stateBox = {
|
||||
};
|
||||
export const StateBox: FC<stateBox> = (props) => {
|
||||
const { text, title, style, mode, endText } = props;
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useResponsive();
|
||||
return (
|
||||
<View style={{ ...(mode == 2 ? boxStyle2 : boxStyle), ...style }}>
|
||||
<Text style={{ fontSize: 12, color: "#0099CC" }}>{title}</Text>
|
||||
<View style={{ ...(mode == 2 ? boxStyle2 : boxStyle), backgroundColor: colors.surface, ...style }}>
|
||||
<Text style={{ fontSize: fontScale(12), color: colors.textAccent }}>{title}</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
<View style={{ flexDirection: mode == 2 ? "row" : "column" }}>
|
||||
<Text style={mode == 2 ? boxTextStyle2 : boxTextStyle}>{text}</Text>
|
||||
<Text style={[mode == 2 ? { fontSize: fontScale(18), textAlign: "right" as const } : { fontSize: fontScale(25), textAlign: "right" as const }, { color: colors.textAccent }]}>{text}</Text>
|
||||
</View>
|
||||
{endText && (
|
||||
<View style={{ flexDirection: mode == 2 ? "row" : "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
...{ ...(mode == 2 ? boxTextStyle2 : boxTextStyle) },
|
||||
fontSize: 10,
|
||||
...(mode == 2 ? { fontSize: fontScale(18), textAlign: "right" as const } : { fontSize: fontScale(25), textAlign: "right" as const }),
|
||||
fontSize: fontScale(10),
|
||||
color: colors.textAccent,
|
||||
}}
|
||||
>
|
||||
{endText}
|
||||
@@ -34,26 +39,13 @@ export const StateBox: FC<stateBox> = (props) => {
|
||||
};
|
||||
const boxStyle: ViewStyle = {
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 10,
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
};
|
||||
const boxStyle2: ViewStyle = {
|
||||
flex: 1,
|
||||
backgroundColor: "white",
|
||||
borderRadius: 10,
|
||||
padding: 5,
|
||||
margin: 5,
|
||||
};
|
||||
const boxTextStyle2: TextStyle = {
|
||||
fontSize: 18,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
|
||||
const boxTextStyle: TextStyle = {
|
||||
fontSize: 25,
|
||||
color: "#0099CC",
|
||||
textAlign: "right",
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
import { View, TouchableOpacity, useWindowDimensions } from "react-native";
|
||||
import { View, TouchableOpacity, Text } from "react-native";
|
||||
import { StateBox } from "./StateBox";
|
||||
import { PositionBox } from "./PositionBox";
|
||||
import { useDeviceOrientationChange } from "../../../stateBox/useDeviceOrientationChange";
|
||||
@@ -33,7 +33,6 @@ export const TrainDataView:FC<props> = ({
|
||||
}) => {
|
||||
const { stationList } = useStationList();
|
||||
|
||||
const { width, height } = useWindowDimensions();
|
||||
const { isLandscape } = useDeviceOrientationChange();
|
||||
const { setInjectData } = useCurrentTrain();
|
||||
|
||||
@@ -129,6 +128,8 @@ export const TrainDataView:FC<props> = ({
|
||||
setDialog(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<StationPosPushDialog
|
||||
@@ -153,7 +154,7 @@ export const TrainDataView:FC<props> = ({
|
||||
flexDirection: "row",
|
||||
//minHeight: 200,
|
||||
//height: heightPercentageToDP("20%"),
|
||||
width: isLandscape ? (width / 100) * 40 : width,
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -18,13 +18,15 @@ type ColorScheme = {
|
||||
* @param isCanceled 運休かどうか
|
||||
* @param isDelayed 遅延しているかどうか
|
||||
* @param isNotService 回送列車かどうか
|
||||
* @param isDark ダークモードかどうか
|
||||
*/
|
||||
export const getStopListColors = (
|
||||
isThrough: boolean,
|
||||
isCommunity: boolean,
|
||||
isCanceled: boolean,
|
||||
isDelayed: boolean,
|
||||
isNotService: boolean = false
|
||||
isNotService: boolean = false,
|
||||
isDark: boolean = false
|
||||
): ColorScheme => {
|
||||
// 最優先: 回送列車の場合
|
||||
if (isNotService) {
|
||||
@@ -32,45 +34,47 @@ export const getStopListColors = (
|
||||
// 回送 + 運休
|
||||
if (isThrough) {
|
||||
return {
|
||||
background: '#1a1a1a', // 非常に濃いグレー
|
||||
text: isCommunity ? '#8090c0' : '#909090', // 暗めの青灰色 or 暗めのグレー
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#aa7799' : '#aa7777') // 遅延時: 暗めのピンク系の赤
|
||||
: (isCommunity ? '#8090c0' : '#909090'),
|
||||
seText: isCommunity ? '#8090c0' : '#909090',
|
||||
background: isDark ? '#111111' : '#1a1a1a',
|
||||
text: isCommunity ? '#8090c0' : (isDark ? '#707070' : '#909090'),
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#aa7799' : '#aa7777')
|
||||
: (isCommunity ? '#8090c0' : (isDark ? '#707070' : '#909090')),
|
||||
seText: isCommunity ? '#8090c0' : (isDark ? '#707070' : '#909090'),
|
||||
opacity: '0.5',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
background: '#3a3a3a', // 濃いグレー
|
||||
text: isCommunity ? '#8090c0' : '#b0b0b0', // 暗めの青灰色 or 明るめのグレー
|
||||
background: isDark ? '#2a2a2a' : '#3a3a3a',
|
||||
text: isCommunity ? '#8090c0' : (isDark ? '#c0c0c0' : '#b0b0b0'),
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#bb8899' : '#bb8888') // 遅延時: 暗めのピンク系の赤
|
||||
: (isCommunity ? '#8090c0' : '#b0b0b0'),
|
||||
seText: isCommunity ? '#8090c0' : '#b0b0b0',
|
||||
? (isCommunity ? '#bb8899' : '#bb8888')
|
||||
: (isCommunity ? '#8090c0' : (isDark ? '#c0c0c0' : '#b0b0b0')),
|
||||
seText: isCommunity ? '#8090c0' : (isDark ? '#c0c0c0' : '#b0b0b0'),
|
||||
opacity: '0.8',
|
||||
};
|
||||
}
|
||||
} else if (isThrough) {
|
||||
// 回送 + 通過
|
||||
return {
|
||||
background: '#e8e8e8', // 薄いグレー
|
||||
text: isCommunity ? '#6677cc' : '#777777', // 暗めの青 or グレー
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#cc5577' : '#dd5555') // 遅延時: 暗めの赤
|
||||
: (isCommunity ? '#6677cc' : '#777777'),
|
||||
seText: isCommunity ? '#6677cc' : '#777777',
|
||||
background: isDark ? '#2a2a2a' : '#e8e8e8',
|
||||
text: isCommunity ? (isDark ? '#8899ee' : '#6677cc') : (isDark ? '#aaaaaa' : '#777777'),
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#cc5577' : '#dd5555')
|
||||
: (isCommunity ? (isDark ? '#8899ee' : '#6677cc') : (isDark ? '#aaaaaa' : '#777777')),
|
||||
seText: isCommunity ? (isDark ? '#8899ee' : '#6677cc') : (isDark ? '#aaaaaa' : '#777777'),
|
||||
opacity: '0.6',
|
||||
};
|
||||
} else {
|
||||
// 回送 + 通常停車
|
||||
return {
|
||||
background: isDelayed ? '#f5f0f0' : '#f5f5f5', // 遅延時は少し赤みのあるグレー
|
||||
text: isCommunity ? '#4455aa' : '#555555', // 暗めの青 or ダークグレー
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#bb3355' : '#cc0000') // 遅延時: 暗めの赤
|
||||
: (isCommunity ? '#4455aa' : '#555555'),
|
||||
seText: isCommunity ? '#4455aa' : '#555555',
|
||||
background: isDark
|
||||
? (isDelayed ? '#2a1a1a' : '#222222')
|
||||
: (isDelayed ? '#f5f0f0' : '#f5f5f5'),
|
||||
text: isCommunity ? (isDark ? '#7788cc' : '#4455aa') : (isDark ? '#aaaaaa' : '#555555'),
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#bb3355' : '#cc0000')
|
||||
: (isCommunity ? (isDark ? '#7788cc' : '#4455aa') : (isDark ? '#aaaaaa' : '#555555')),
|
||||
seText: isCommunity ? (isDark ? '#7788cc' : '#4455aa') : (isDark ? '#aaaaaa' : '#555555'),
|
||||
opacity: '0.85',
|
||||
};
|
||||
}
|
||||
@@ -81,10 +85,10 @@ export const getStopListColors = (
|
||||
if (isThrough) {
|
||||
// 通過系 + 運休
|
||||
return {
|
||||
background: '#2a2a2a', // 濃いグレー
|
||||
text: isCommunity ? '#a8b5ff' : '#c0c0c0', // 薄い青 or 薄いグレー
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#dd99bb' : '#dd9999') // 遅延時: 薄いピンク系の赤
|
||||
background: isDark ? '#1e1e2e' : '#2a2a2a',
|
||||
text: isCommunity ? '#a8b5ff' : '#c0c0c0',
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#dd99bb' : '#dd9999')
|
||||
: (isCommunity ? '#a8b5ff' : '#c0c0c0'),
|
||||
seText: isCommunity ? '#a8b5ff' : '#c0c0c0',
|
||||
opacity: '0.6',
|
||||
@@ -92,10 +96,10 @@ export const getStopListColors = (
|
||||
} else {
|
||||
// 通常停車 + 運休
|
||||
return {
|
||||
background: '#5a5a5a', // 中程度のグレー
|
||||
text: isCommunity ? '#a8b5ff' : '#ffffff', // 薄い青 or 白
|
||||
background: isDark ? '#3a3a4a' : '#5a5a5a',
|
||||
text: isCommunity ? '#a8b5ff' : '#ffffff',
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#ffaacc' : '#ffaaaa') // 遅延時: 明るいピンク系の赤
|
||||
? (isCommunity ? '#ffaacc' : '#ffaaaa')
|
||||
: (isCommunity ? '#a8b5ff' : '#ffffff'),
|
||||
seText: isCommunity ? '#a8b5ff' : '#ffffff',
|
||||
opacity: '0.9',
|
||||
@@ -106,12 +110,14 @@ export const getStopListColors = (
|
||||
// 次: 通過系の場合
|
||||
if (isThrough) {
|
||||
return {
|
||||
background: isCommunity ? '#f0f4ff' : '#fafafa', // 薄い青背景 or 薄いグレー
|
||||
text: isCommunity ? '#5577ff' : '#888888', // 中程度の青 or グレー
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#dd5588' : '#ff6666') // 遅延時: コミュニティは紫がかった赤、通常は明るい赤
|
||||
: (isCommunity ? '#5577ff' : '#888888'),
|
||||
seText: isCommunity ? '#5577ff' : '#888888',
|
||||
background: isDark
|
||||
? (isCommunity ? '#1a1e2e' : '#1e1e1e')
|
||||
: (isCommunity ? '#f0f4ff' : '#fafafa'),
|
||||
text: isCommunity ? (isDark ? '#7799ff' : '#5577ff') : (isDark ? '#666666' : '#888888'),
|
||||
timeText: isDelayed
|
||||
? (isCommunity ? '#dd5588' : '#ff6666')
|
||||
: (isCommunity ? (isDark ? '#7799ff' : '#5577ff') : (isDark ? '#666666' : '#888888')),
|
||||
seText: isCommunity ? (isDark ? '#7799ff' : '#5577ff') : (isDark ? '#666666' : '#888888'),
|
||||
opacity: '0.5',
|
||||
};
|
||||
}
|
||||
@@ -120,19 +126,27 @@ export const getStopListColors = (
|
||||
if (isCommunity) {
|
||||
// コミュニティ投稿
|
||||
return {
|
||||
background: isDelayed ? '#fff5f5' : '#ffffff', // 遅延時は薄い赤背景
|
||||
text: '#3355dd', // 明確な青
|
||||
timeText: isDelayed ? '#cc2266' : '#3355dd', // 遅延時: 紫がかった赤
|
||||
seText: '#3355dd',
|
||||
background: isDark
|
||||
? (isDelayed ? '#2a1a2a' : '#1e1e2e')
|
||||
: (isDelayed ? '#fff5f5' : '#ffffff'),
|
||||
text: isDark ? '#8899ff' : '#3355dd',
|
||||
timeText: isDelayed
|
||||
? (isDark ? '#ee4488' : '#cc2266')
|
||||
: (isDark ? '#8899ff' : '#3355dd'),
|
||||
seText: isDark ? '#8899ff' : '#3355dd',
|
||||
opacity: '0.95',
|
||||
};
|
||||
} else {
|
||||
// 公式データ
|
||||
return {
|
||||
background: isDelayed ? '#fff5f5' : '#ffffff', // 遅延時は薄い赤背景
|
||||
text: '#000000', // 黒
|
||||
timeText: isDelayed ? '#dd0000' : '#000000', // 遅延時: 標準的な赤
|
||||
seText: '#000000',
|
||||
background: isDark
|
||||
? (isDelayed ? '#2a1a1a' : '#1e1e2e')
|
||||
: (isDelayed ? '#fff5f5' : '#ffffff'),
|
||||
text: isDark ? '#e0e0e0' : '#000000',
|
||||
timeText: isDelayed
|
||||
? (isDark ? '#ff5555' : '#dd0000')
|
||||
: (isDark ? '#e0e0e0' : '#000000'),
|
||||
seText: isDark ? '#e0e0e0' : '#000000',
|
||||
opacity: '0.95',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
BackHandler,
|
||||
Linking,
|
||||
} from "react-native";
|
||||
import { SheetManager, useScrollHandlers } from "react-native-actions-sheet";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { getTrainType } from "../../lib/getTrainType";
|
||||
import { customTrainDataDetector } from "../custom-train-data";
|
||||
import { useDeviceOrientationChange } from "../../stateBox/useDeviceOrientationChange";
|
||||
@@ -22,7 +22,9 @@ import { ShowSpecialTrain } from "./EachTrainInfo/ShowSpecialTrain";
|
||||
import { useTrainMenu } from "../../stateBox/useTrainMenu";
|
||||
import { HeaderText } from "./EachTrainInfoCore/HeaderText";
|
||||
import { useStationList } from "../../stateBox/useStationList";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
// Custom hooks
|
||||
import { useTrainDiagramData } from "./EachTrainInfoCore/hooks/useTrainDiagramData";
|
||||
@@ -42,14 +44,13 @@ export const EachTrainInfoCore = ({
|
||||
}) => {
|
||||
const { stationList } = useStationList();
|
||||
const { allCustomTrainData } = useAllTrainDiagram();
|
||||
const { colors, fixed } = useThemeColors();
|
||||
const { verticalScale, moderateScale } = useResponsive();
|
||||
const { setTrainInfo } = useTrainMenu();
|
||||
const { height } = useWindowDimensions();
|
||||
const { isLandscape } = useDeviceOrientationChange();
|
||||
|
||||
const scrollHandlers = actionSheetRef
|
||||
? //@ts-ignore
|
||||
useScrollHandlers("scrollview-1", actionSheetRef)
|
||||
: null;
|
||||
const scrollRef = useRef<any>(null);
|
||||
// Custom hooks for data management
|
||||
const { trainData, setTrainData, trueTrainID } = useTrainDiagramData(
|
||||
data.trainNum
|
||||
@@ -78,7 +79,7 @@ export const EachTrainInfoCore = ({
|
||||
useAutoScroll(
|
||||
points,
|
||||
trainDataWithThrough,
|
||||
scrollHandlers,
|
||||
scrollRef,
|
||||
isJumped,
|
||||
setIsJumped,
|
||||
setShowThrew
|
||||
@@ -125,7 +126,6 @@ export const EachTrainInfoCore = ({
|
||||
} else {
|
||||
SheetManager.hide("EachTrainInfo").then(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error - SheetManager payload type is too restrictive
|
||||
SheetManager.show("EachTrainInfo", { payload });
|
||||
}, 200);
|
||||
});
|
||||
@@ -135,20 +135,20 @@ export const EachTrainInfoCore = ({
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#0099CC",
|
||||
backgroundColor: fixed.primary,
|
||||
borderTopLeftRadius: 5,
|
||||
borderTopRightRadius: 5,
|
||||
borderColor: "dark",
|
||||
borderColor: colors.border,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<View style={{ height: 26, width: "100%" }}>
|
||||
<View style={{ height: verticalScale(26), width: "100%" }}>
|
||||
<View
|
||||
style={{
|
||||
height: 6,
|
||||
width: 45,
|
||||
height: verticalScale(6),
|
||||
width: moderateScale(45),
|
||||
borderRadius: 100,
|
||||
backgroundColor: "#f0f0f0",
|
||||
backgroundColor: colors.borderLight,
|
||||
marginVertical: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
@@ -164,18 +164,18 @@ export const EachTrainInfoCore = ({
|
||||
navigate={navigate}
|
||||
from={from}
|
||||
fontLoaded={true}
|
||||
scrollHandlers={scrollHandlers}
|
||||
scrollRef={scrollRef}
|
||||
/>
|
||||
|
||||
<DynamicHeaderScrollView
|
||||
from={from}
|
||||
styles={styles as any}
|
||||
scrollHandlers={scrollHandlers}
|
||||
scrollRef={scrollRef}
|
||||
containerProps={{
|
||||
style: {
|
||||
maxHeight: isLandscape ? height - 94 : (height / 100) * 70,
|
||||
maxHeight: height * 0.7,
|
||||
backgroundColor:
|
||||
customTrainType.data === "notService" ? "#777777ff" : "white",
|
||||
customTrainType.data === "notService" ? "#777777ff" : colors.surface,
|
||||
},
|
||||
}}
|
||||
shortHeader={
|
||||
@@ -207,7 +207,7 @@ export const EachTrainInfoCore = ({
|
||||
}
|
||||
>
|
||||
{customTrainType.data === "notService" && (
|
||||
<Text style={{ backgroundColor: "#ffffffc2", fontWeight: "bold" }}>
|
||||
<Text style={{ backgroundColor: colors.surface, color: colors.text, fontWeight: "bold" }}>
|
||||
この列車には乗車できません。
|
||||
</Text>
|
||||
)}
|
||||
@@ -219,10 +219,10 @@ export const EachTrainInfoCore = ({
|
||||
onPress={() =>
|
||||
extendToHeadStation(item.station, item.dia, index)
|
||||
}
|
||||
style={styles.extendStationButton}
|
||||
style={[styles.extendStationButton, { borderColor: colors.textAccent }]}
|
||||
key={`${item.station}-head${index}`}
|
||||
>
|
||||
<Text style={styles.extendStationText}>
|
||||
<Text style={[styles.extendStationText, { color: colors.text }]}>
|
||||
「本当の始発駅」を表示
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -238,9 +238,9 @@ export const EachTrainInfoCore = ({
|
||||
onPress={() =>
|
||||
Linking.openURL(`https://twitter.com/search?q=${data.trainNum}`)
|
||||
}
|
||||
style={styles.twitterSearchButton}
|
||||
style={[styles.twitterSearchButton, { borderColor: colors.textAccent, backgroundColor: colors.backgroundOverlay }]}
|
||||
>
|
||||
<Text style={styles.extendStationText}>Twitterで検索</Text>
|
||||
<Text style={[styles.extendStationText, { color: colors.text }]}>Twitterで検索</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{trainDataWithThrough.map((item, index, array) =>
|
||||
@@ -251,7 +251,7 @@ export const EachTrainInfoCore = ({
|
||||
openTrainInfo={openTrainInfo}
|
||||
/>
|
||||
) : item.split(",")[1].includes(".") ? (
|
||||
<></>
|
||||
<React.Fragment key={`${item}-skip`} />
|
||||
) : (
|
||||
<EachStopList
|
||||
i={item}
|
||||
@@ -267,7 +267,7 @@ export const EachTrainInfoCore = ({
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Text style={styles.customDataNote}>
|
||||
<Text style={[styles.customDataNote, { color: colors.textSecondary }]}>
|
||||
時刻が斜体,青色になっている時刻はコミュニティで追加されている独自データです。
|
||||
</Text>
|
||||
{tailStation.length > 0 &&
|
||||
@@ -276,17 +276,17 @@ export const EachTrainInfoCore = ({
|
||||
!showTailStation.includes(index) && (
|
||||
<TouchableOpacity
|
||||
onPress={() => extendToTailStation(station, dia, index)}
|
||||
style={styles.extendStationButton}
|
||||
style={[styles.extendStationButton, { borderColor: colors.textAccent }]}
|
||||
key={`${station}-tail${index}`}
|
||||
>
|
||||
<Text style={styles.extendStationText}>
|
||||
<Text style={[styles.extendStationText, { color: colors.text }]}>
|
||||
「本当の終着駅」を表示
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
|
||||
<View style={styles.bottomSpacer} />
|
||||
<View style={[styles.bottomSpacer, { borderBottomColor: colors.borderLight }]} />
|
||||
</DynamicHeaderScrollView>
|
||||
</View>
|
||||
);
|
||||
@@ -320,7 +320,6 @@ const styles = StyleSheet.create({
|
||||
extendStationText: {
|
||||
fontSize: 18,
|
||||
fontWeight: "bold",
|
||||
color: "black",
|
||||
},
|
||||
twitterSearchButton: {
|
||||
padding: 10,
|
||||
@@ -333,14 +332,12 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: "#ffffffc2",
|
||||
},
|
||||
customDataNote: {
|
||||
backgroundColor: "#ffffffc2",
|
||||
},
|
||||
bottomSpacer: {
|
||||
flexDirection: "row",
|
||||
padding: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#f0f0f0",
|
||||
backgroundColor: "#ffffffc2",
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import React, { FC, useMemo } from "react";
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import React, { FC, useMemo, useState } from "react";
|
||||
import { Text, View, TextStyle, TouchableOpacity } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { migrateTrainName } from "../../../lib/eachTrainInfoCoreLib/migrateTrainName";
|
||||
import { TrainIconStatus } from "./trainIconStatus";
|
||||
@@ -19,6 +13,10 @@ import { getStringConfig } from "@/lib/getStringConfig";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { getPDFViewURL } from "@/lib/getPdfViewURL";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
import { useUnyohub } from "@/stateBox/useUnyohub";
|
||||
import { useElesite } from "@/stateBox/useElesite";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
|
||||
type Props = {
|
||||
data: { trainNum: string; limited: string };
|
||||
@@ -30,13 +28,12 @@ type Props = {
|
||||
navigate: NavigateFunction;
|
||||
from: string;
|
||||
fontLoaded: boolean;
|
||||
scrollHandlers: any;
|
||||
scrollRef: any;
|
||||
};
|
||||
|
||||
const textConfig: TextStyle = {
|
||||
fontSize: 17,
|
||||
fontWeight: "bold",
|
||||
color: "white",
|
||||
};
|
||||
|
||||
export const HeaderText: FC<Props> = ({
|
||||
@@ -48,15 +45,30 @@ export const HeaderText: FC<Props> = ({
|
||||
tailStation,
|
||||
navigate,
|
||||
from,
|
||||
scrollHandlers,
|
||||
scrollRef,
|
||||
}) => {
|
||||
const { limited, trainNum } = data;
|
||||
|
||||
const { height, width } = useWindowDimensions();
|
||||
const { fixed } = useThemeColors();
|
||||
const { fontScale } = useResponsive();
|
||||
const { updatePermission } = useTrainMenu();
|
||||
const { allCustomTrainData, getTodayOperationByTrainId } =
|
||||
useAllTrainDiagram();
|
||||
const { expoPushToken } = useNotification();
|
||||
const {
|
||||
getUnyohubByTrainNumber,
|
||||
getUnyohubEntriesByTrainNumber,
|
||||
useUnyohub: unyohubEnabled,
|
||||
} = useUnyohub();
|
||||
const { getElesiteEntriesByTrainNumber, useElesite: elesiteEnabled } =
|
||||
useElesite();
|
||||
|
||||
// 追加ソースのON/OFFをここで管理(将来ソースが増えたらここに足す)
|
||||
const additionalSources = {
|
||||
unyohub: unyohubEnabled,
|
||||
elesite: elesiteEnabled,
|
||||
};
|
||||
const hasAdditionalSources = Object.values(additionalSources).some(Boolean);
|
||||
|
||||
// 列車名、種別、フォントの取得
|
||||
const [
|
||||
@@ -68,7 +80,10 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
trainInfoUrl,
|
||||
directions,
|
||||
customTrainData,
|
||||
] = useMemo(() => {
|
||||
const result = customTrainDataDetector(trainNum, allCustomTrainData);
|
||||
const {
|
||||
type,
|
||||
train_name,
|
||||
@@ -78,15 +93,14 @@ export const HeaderText: FC<Props> = ({
|
||||
uwasa,
|
||||
train_info_url,
|
||||
to_data,
|
||||
} = customTrainDataDetector(trainNum, allCustomTrainData);
|
||||
directions,
|
||||
} = result;
|
||||
const [typeString, fontAvailable, isOneMan] = getStringConfig(
|
||||
type,
|
||||
trainNum,
|
||||
);
|
||||
switch (true) {
|
||||
case train_name !== "":
|
||||
// 特急の場合は、列車名を取得
|
||||
// 列番対称データがある場合はそれから列車番号を取得
|
||||
return [
|
||||
typeString,
|
||||
train_name +
|
||||
@@ -99,6 +113,8 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
result,
|
||||
];
|
||||
case trainData[trainData.length - 1] === undefined:
|
||||
return [
|
||||
@@ -110,9 +126,10 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
result,
|
||||
];
|
||||
case to_data && to_data !== "":
|
||||
// 行先がある場合は、行先を取得
|
||||
return [
|
||||
typeString,
|
||||
to_data + "行き",
|
||||
@@ -122,9 +139,10 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
result,
|
||||
];
|
||||
default:
|
||||
// 行先がある場合は、行先を取得
|
||||
return [
|
||||
typeString,
|
||||
migrateTrainName(
|
||||
@@ -136,18 +154,53 @@ export const HeaderText: FC<Props> = ({
|
||||
priority,
|
||||
uwasa,
|
||||
train_info_url,
|
||||
directions,
|
||||
result,
|
||||
];
|
||||
}
|
||||
}, [trainData]);
|
||||
}, [trainData, trainNum, allCustomTrainData]);
|
||||
|
||||
const todayOperation = getTodayOperationByTrainId(trainNum).filter(
|
||||
(d) => d.state !== 100,
|
||||
);
|
||||
|
||||
let iconTrainDirection =
|
||||
parseInt(trainNum.replace(/[^\d]/g, "")) % 2 == 0 ? true : false;
|
||||
if (directions != undefined) {
|
||||
iconTrainDirection = directions ? true : false;
|
||||
}
|
||||
|
||||
const unyohubLookupNum = customTrainData?.train_number_override || trainNum;
|
||||
const unyohubFormation = getUnyohubByTrainNumber(unyohubLookupNum);
|
||||
const unyohubEntries = getUnyohubEntriesByTrainNumber(unyohubLookupNum);
|
||||
const elesiteEntries = getElesiteEntriesByTrainNumber(trainNum);
|
||||
|
||||
// 車番(formations)が空でないエントリが1件以上あれば「運用Hub情報あり」と判定
|
||||
const hasUnyohubFormation = unyohubEntries.some(
|
||||
(e) => !!e.formations && e.formations.trim() !== "",
|
||||
);
|
||||
const hasElesiteFormation = elesiteEntries.some(
|
||||
(e) => (e.formation_config?.units?.length ?? 0) > 0,
|
||||
);
|
||||
|
||||
const hasExtraInfo =
|
||||
priority > 200 ||
|
||||
todayOperation?.length > 0 ||
|
||||
hasUnyohubFormation ||
|
||||
hasElesiteFormation;
|
||||
|
||||
const [isWrapped, setIsWrapped] = useState(false);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
|
||||
style={{
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
onTouchStart={() =>
|
||||
scrollHandlers.ref.current?.scrollTo({ y: 0, animated: true })
|
||||
scrollRef.current?.scrollTo({ y: 0, animated: true })
|
||||
}
|
||||
>
|
||||
<TrainIconStatus
|
||||
@@ -155,18 +208,42 @@ export const HeaderText: FC<Props> = ({
|
||||
navigate={navigate}
|
||||
from={from}
|
||||
todayOperation={todayOperation}
|
||||
direction={iconTrainDirection}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 2,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: fontScale(20),
|
||||
color: fixed.textOnPrimary,
|
||||
fontFamily: fontAvailable ? "JR-Nishi" : undefined,
|
||||
fontWeight: !fontAvailable ? "bold" : undefined,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
{isWrapped ? typeName.replace(/(.{2})/g, "$1\n").trim() : typeName}
|
||||
</Text>
|
||||
{isOneMan && <OneManText />}
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flexShrink: 1,
|
||||
flexWrap: "wrap",
|
||||
...(trainInfoUrl
|
||||
? {
|
||||
borderWidth: 0,
|
||||
borderBottomWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: "white",
|
||||
borderColor: fixed.textOnPrimary,
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
@@ -180,64 +257,59 @@ export const HeaderText: FC<Props> = ({
|
||||
}}
|
||||
disabled={!trainInfoUrl}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
color: "white",
|
||||
fontFamily: fontAvailable ? "JR-Nishi" : undefined,
|
||||
fontWeight: !fontAvailable ? "bold" : undefined,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
{typeName}
|
||||
</Text>
|
||||
{isOneMan && <OneManText />}
|
||||
<Text
|
||||
style={{
|
||||
...textConfig,
|
||||
...(trainName.length > 10 ? { fontSize: 14 } : {}),
|
||||
maxWidth: width * 0.6,
|
||||
color: fixed.textOnPrimary,
|
||||
...(trainName.length > 10 ? { fontSize: fontScale(16) } : { fontSize: fontScale(17) }),
|
||||
flexShrink: 1,
|
||||
}}
|
||||
onTextLayout={(e) => {
|
||||
if (e.nativeEvent.lines.length > 1) setIsWrapped(true);
|
||||
}}
|
||||
>
|
||||
{trainName}
|
||||
</Text>
|
||||
<InfogramText infogram={infogram} />
|
||||
{/* {trainInfoUrl && (
|
||||
<MaterialCommunityIcons
|
||||
name={"open-in-new"}
|
||||
color="white"
|
||||
size={15}
|
||||
/>
|
||||
)} */}
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ flex: 1 }} />
|
||||
<TouchableOpacity
|
||||
onLongPress={() => {
|
||||
if (!updatePermission) return;
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
}}
|
||||
disabled={!updatePermission}
|
||||
>
|
||||
<Text style={textConfig}>
|
||||
{showHeadStation.map((d) => `${headStation[d].id} + `)}
|
||||
{trainNum}
|
||||
{showTailStation.map((d) => ` + ${tailStation[d].id}`)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={{ ...textConfig, fontSize: fontScale(17), color: fixed.textOnPrimary }}>
|
||||
{showHeadStation.map((d) => `${headStation[d].id} + `)}
|
||||
{trainNum}
|
||||
{showTailStation.map((d) => ` + ${tailStation[d].id}`)}
|
||||
</Text>
|
||||
<MaterialCommunityIcons
|
||||
name="database"
|
||||
color={
|
||||
priority > 200 || todayOperation?.length > 0 ? "yellow" : "white"
|
||||
}
|
||||
color={hasExtraInfo ? "yellow" : fixed.textOnPrimary}
|
||||
size={30}
|
||||
style={{ margin: 5 }}
|
||||
onPress={() => {
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
if (hasAdditionalSources) {
|
||||
(SheetManager.show as any)("TrainDataSources", {
|
||||
payload: {
|
||||
trainNum,
|
||||
unyohubTrainNum: unyohubLookupNum,
|
||||
unyohubEntries,
|
||||
elesiteEntries,
|
||||
todayOperation,
|
||||
navigate,
|
||||
expoPushToken,
|
||||
priority,
|
||||
direction: iconTrainDirection,
|
||||
customTrainData,
|
||||
typeName,
|
||||
trainName,
|
||||
departureStation: trainData[0]?.split(",")[0] ?? "",
|
||||
destinationStation:
|
||||
trainData[trainData.length - 1]?.split(",")[0] ?? "",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 追加ソースが全てオフ → 元の挙動(直接 DB ページを開く)
|
||||
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
|
||||
navigate("generalWebView", { uri, useExitButton: false });
|
||||
SheetManager.hide("EachTrainInfo");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import React, { FC } from "react";
|
||||
import { Text } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
type props = {
|
||||
infogram: string;
|
||||
}
|
||||
export const InfogramText: FC<props> = ({infogram}) => {
|
||||
const { fixed } = useThemeColors();
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 20,
|
||||
color: "white",
|
||||
color: fixed.textOnPrimary,
|
||||
fontFamily: "JNR-font",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { FC } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
export const OneManText: FC = () => {
|
||||
const { fixed } = useThemeColors();
|
||||
const styles = {
|
||||
fontSize: 12,
|
||||
margin: -2,
|
||||
color: "white",
|
||||
color: fixed.textOnPrimary,
|
||||
fontFamily: "Zou",
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
import React,{ useEffect, MutableRefObject } from 'react';
|
||||
import { LayoutAnimation, ScrollView } from 'react-native';
|
||||
import { useEffect, MutableRefObject } from 'react';
|
||||
import { InteractionManager } from 'react-native';
|
||||
|
||||
|
||||
export const useAutoScroll = (
|
||||
points: boolean[] | undefined,
|
||||
trainDataWithThrough: string[],
|
||||
scrollHandlers: any,
|
||||
scrollRef: MutableRefObject<any>,
|
||||
isJumped: boolean,
|
||||
setIsJumped: (value: boolean) => void,
|
||||
setShowThrew: (value: boolean) => void
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (isJumped || !points?.length || !scrollHandlers) return;
|
||||
if (isJumped || !points?.length || !scrollRef) return;
|
||||
|
||||
const currentPositionIndex = points.findIndex((d) => d === true);
|
||||
if (currentPositionIndex === -1) return;
|
||||
|
||||
setShowThrew(true);
|
||||
// ActionSheetの開閉アニメーション完了後にレイアウト変更を行う
|
||||
const handle = InteractionManager.runAfterInteractions(() => {
|
||||
setShowThrew(true);
|
||||
|
||||
const isPassingThrough = trainDataWithThrough[currentPositionIndex]?.split(',')[1] === '通過';
|
||||
if (isPassingThrough) {
|
||||
LayoutAnimation.configureNext({
|
||||
duration: 400,
|
||||
update: { type: 'easeInEaseOut', springDamping: 0.6 },
|
||||
});
|
||||
}
|
||||
// 5駅以内の場合はスクロールしない
|
||||
if (currentPositionIndex < 5) {
|
||||
setIsJumped(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5駅以内の場合はスクロールしない
|
||||
if (currentPositionIndex < 5) {
|
||||
setIsJumped(true);
|
||||
return;
|
||||
}
|
||||
const scrollPosition = currentPositionIndex * 44 - 50;
|
||||
setTimeout(() => {
|
||||
scrollRef.current?.scrollTo({ y: scrollPosition, animated: true });
|
||||
setIsJumped(true);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const scrollPosition = currentPositionIndex * 44 - 50;
|
||||
setTimeout(() => {
|
||||
scrollHandlers.ref.current?.scrollTo({ y: scrollPosition, animated: true });
|
||||
setIsJumped(true);
|
||||
}, 400);
|
||||
}, [points, trainDataWithThrough, scrollHandlers, isJumped, setIsJumped, setShowThrew]);
|
||||
return () => handle.cancel();
|
||||
}, [points, trainDataWithThrough, scrollRef, isJumped, setIsJumped, setShowThrew]);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useTrainPosition = (
|
||||
const trainPosData = getCurrentStationData(trainNum);
|
||||
if (trainPosData) {
|
||||
logger.debug('Train position data:', trainPosData);
|
||||
setCurrentTrainData(trainPosData);
|
||||
setCurrentTrainData(trainPosData as TrainData);
|
||||
}
|
||||
}, [currentTrain, trainNum, getCurrentStationData]);
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ import React, { ComponentProps, FC, useEffect, useState } from "react";
|
||||
import { View, Image, TouchableOpacity } from "react-native";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useInterval } from "../../../lib/useInterval";
|
||||
import { Icon } from "@expo/vector-icons/build/createIconSet";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { customTrainDataDetector } from "../../custom-train-data";
|
||||
import { useAllTrainDiagram } from "@/stateBox/useAllTrainDiagram";
|
||||
import { useInterval } from "@/lib/useInterval";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
import { OperationLogs } from "@/lib/CommonTypes";
|
||||
|
||||
@@ -17,17 +16,18 @@ type Props = {
|
||||
navigate: NavigateFunction;
|
||||
from: string;
|
||||
todayOperation: OperationLogs[];
|
||||
direction?: boolean;
|
||||
};
|
||||
type apt = {
|
||||
name: GlyphNames;
|
||||
color: string;
|
||||
};
|
||||
export const TrainIconStatus: FC<Props> = (props) => {
|
||||
const { data, navigate, from, todayOperation } = props;
|
||||
const { data, navigate, from, todayOperation, direction } = props;
|
||||
const [anpanmanStatus, setAnpanmanStatus] = useState<apt>();
|
||||
const { allCustomTrainData } = useAllTrainDiagram();
|
||||
const [trainIconData, setTrainIcon] = useState<
|
||||
{ vehicle_info_img: string; vehicle_info_url: string }[]
|
||||
{ vehicle_info_img: string;vehicle_info_right_img: string; vehicle_info_url: string }[]
|
||||
>([]);
|
||||
useEffect(() => {
|
||||
if (!data.trainNum) return;
|
||||
@@ -53,7 +53,7 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
...(operation.train_ids || []),
|
||||
...(operation.related_train_ids || []),
|
||||
];
|
||||
|
||||
|
||||
// data.trainNumの接頭辞と一致するものを探す
|
||||
for (const trainId of allTrainIds) {
|
||||
const prefix = trainId.split(',')[0]; // カンマ前の部分
|
||||
@@ -63,30 +63,58 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
const aTrainId = findMatchingTrainId(a);
|
||||
const bTrainId = findMatchingTrainId(b);
|
||||
|
||||
|
||||
// マッチしたものがない場合は元の順序を保持
|
||||
if (!aTrainId || !bTrainId) {
|
||||
return aTrainId ? -1 : bTrainId ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
const aOrder = extractOrderNumber(aTrainId);
|
||||
const bOrder = extractOrderNumber(bTrainId);
|
||||
|
||||
|
||||
return aOrder - bOrder;
|
||||
})
|
||||
.map((op) => ({
|
||||
vehicle_info_img: op.vehicle_img || vehicle_info_img,
|
||||
vehicle_info_right_img: op.vehicle_img_right || op.vehicle_img || vehicle_info_img,
|
||||
vehicle_info_url: op.vehicle_info_url,
|
||||
})) || [];
|
||||
setTrainIcon(returnData);
|
||||
} else if (vehicle_info_img) {
|
||||
setTrainIcon([{ vehicle_info_img, vehicle_info_url }]);
|
||||
setTrainIcon([{ vehicle_info_img, vehicle_info_right_img: vehicle_info_img, vehicle_info_url }]);
|
||||
}
|
||||
|
||||
// アンパンマンステータスAPIのエンドポイント判定
|
||||
let anpanmanApiPath: string | null = null;
|
||||
switch (data.trainNum) {
|
||||
// 予讃線 → yosan-anpanman
|
||||
// しおかぜ 8000 アンパン
|
||||
case "10M":
|
||||
case "22M":
|
||||
case "9M":
|
||||
case "21M":
|
||||
// いしづち 8000 アンパン
|
||||
case "1010M":
|
||||
case "1022M":
|
||||
case "1009M":
|
||||
case "1021M":
|
||||
// いしづち 三桁 アンパン
|
||||
case "1041M":
|
||||
case "1044M":
|
||||
// 宇和海 2000 アンパン
|
||||
case "1058D":
|
||||
case "1066D":
|
||||
case "1074D":
|
||||
case "1053D":
|
||||
case "1059D":
|
||||
case "1067D":
|
||||
anpanmanApiPath = "yosan-anpanman";
|
||||
break;
|
||||
// 土讃線 → dosan-anpanman
|
||||
// 南風 2700 アンパン
|
||||
case "32D":
|
||||
case "36D":
|
||||
case "44D":
|
||||
@@ -97,62 +125,45 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
case "45D":
|
||||
case "49D":
|
||||
case "57D":
|
||||
fetch(
|
||||
`https://n8n.haruk.in/webhook/dosan-anpanman?trainNum=${
|
||||
data.trainNum
|
||||
}&month=${dayjs().format("M")}&day=${dayjs().format("D")}`
|
||||
)
|
||||
.then((d) => d.json())
|
||||
.then((d) => {
|
||||
if (d.trainStatus == "〇") {
|
||||
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
|
||||
} else if (d.trainStatus == "▲") {
|
||||
setAnpanmanStatus({ name: "warning-outline", color: "yellow" });
|
||||
} else if (d.trainStatus == "×") {
|
||||
setAnpanmanStatus({ name: "close-circle-outline", color: "red" });
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "2074D":
|
||||
case "2076D":
|
||||
case "2080D":
|
||||
case "2082D":
|
||||
case "2071D":
|
||||
case "2073D":
|
||||
case "2079D":
|
||||
case "2081D":
|
||||
fetch(
|
||||
`https://n8n.haruk.in/webhook/dosan-anpanman-first?trainNum=${
|
||||
data.trainNum
|
||||
}&month=${dayjs().format("M")}&day=${dayjs().format("D")}`
|
||||
)
|
||||
.then((d) => d.json())
|
||||
.then((d) => {
|
||||
if (d.trainStatus == "〇") {
|
||||
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
|
||||
} else if (d.trainStatus == "▲") {
|
||||
setAnpanmanStatus({ name: "warning-outline", color: "yellow" });
|
||||
} else if (d.trainStatus == "×") {
|
||||
//setAnpanmanStatus({ name: "close-circle-outline", color: "red" });
|
||||
}
|
||||
});
|
||||
anpanmanApiPath = "dosan-anpanman";
|
||||
break;
|
||||
}
|
||||
if (anpanmanApiPath) {
|
||||
fetch(
|
||||
`https://n8n.haruk.in/webhook/${anpanmanApiPath}?trainNum=${
|
||||
data.trainNum
|
||||
}&month=${dayjs().format("M")}&day=${dayjs().format("D")}`
|
||||
)
|
||||
.then((d) => d.json())
|
||||
.then((d) => {
|
||||
if (d.trainStatus == "〇" || d.trainStatus == "○") {
|
||||
//setAnpanmanStatus({name:"checkmark-circle-outline",color:"blue"});
|
||||
} else if (d.trainStatus == "▲") {
|
||||
setAnpanmanStatus({ name: "warning-outline", color: "yellow" });
|
||||
} else if (d.trainStatus == "×") {
|
||||
setAnpanmanStatus({ name: "close-circle-outline", color: "red" });
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}, [data.trainNum, allCustomTrainData, todayOperation]);
|
||||
const [move, setMove] = useState(true);
|
||||
useInterval(
|
||||
() => {
|
||||
// anpanmanStatusがデータを持っているなら実行
|
||||
if (anpanmanStatus) setMove(!move);
|
||||
},
|
||||
1000,
|
||||
true
|
||||
);
|
||||
|
||||
// JSスレッドでの点滅(useInterval + useState)
|
||||
// reanimated の withRepeat はUIスレッドで毎フレーム更新し続けるため
|
||||
// ActionSheetのスプリングアニメーションと競合する
|
||||
const [showIcon, setShowIcon] = useState(false);
|
||||
useInterval(() => {
|
||||
if (anpanmanStatus) {
|
||||
setShowIcon((prev) => !prev);
|
||||
}
|
||||
}, 1000, !!anpanmanStatus);
|
||||
|
||||
return (
|
||||
<>
|
||||
{trainIconData.map(
|
||||
({ vehicle_info_img: trainIcon, vehicle_info_url: address }, index) => (
|
||||
({ vehicle_info_img: trainIcon, vehicle_info_right_img: trainIconRight, vehicle_info_url: address }, index) => (
|
||||
<TouchableOpacity
|
||||
key={`${trainIcon}-${index}`}
|
||||
onPress={() => {
|
||||
navigate("howto", {
|
||||
info: address,
|
||||
@@ -162,26 +173,30 @@ export const TrainIconStatus: FC<Props> = (props) => {
|
||||
}}
|
||||
disabled={!address}
|
||||
>
|
||||
{move ? (
|
||||
<Image
|
||||
source={{ uri: trainIcon }}
|
||||
style={{
|
||||
height: index > 0 ? 15 : 30,
|
||||
width: index > 0 ? 12 : 24,
|
||||
marginRight: 5,
|
||||
marginLeft: index > 0 ? -10 : 0,
|
||||
marginTop: index > 0 ? 10 : 0,
|
||||
//display: index == 0 ? "flex" : "none", //暫定対応:複数アイコンがある場合は最初のアイコンのみ表示
|
||||
}}
|
||||
resizeMethod="resize"
|
||||
/>
|
||||
) : (
|
||||
<Ionicons
|
||||
{...anpanmanStatus}
|
||||
size={24}
|
||||
style={{ marginRight: 5 }}
|
||||
/>
|
||||
)}
|
||||
<View>
|
||||
<View style={{ opacity: anpanmanStatus && showIcon ? 0 : 1 }}>
|
||||
<Image
|
||||
source={{ uri: direction ? trainIcon : trainIconRight || trainIcon }}
|
||||
style={{
|
||||
height: index > 0 ? 15 : 30,
|
||||
width: index > 0 ? 12 : 24,
|
||||
marginRight: 5,
|
||||
marginLeft: index > 0 ? -10 : 0,
|
||||
marginTop: index > 0 ? 10 : 0,
|
||||
}}
|
||||
resizeMethod="resize"
|
||||
/>
|
||||
</View>
|
||||
{anpanmanStatus && showIcon && (
|
||||
<View style={{ position: "absolute", top: 0, left: 0 }}>
|
||||
<Ionicons
|
||||
{...anpanmanStatus}
|
||||
size={24}
|
||||
style={{ marginRight: 5 }}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { LayoutAnimation } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { getType } from "../../../lib/eachTrainInfoCoreLib/getType";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import type { NavigateFunction } from "@/types";
|
||||
|
||||
type Props = {
|
||||
@@ -11,6 +11,7 @@ type Props = {
|
||||
from: string;
|
||||
};
|
||||
export const TrainViewIcon: FC<Props> = ({ data, navigate, from }) => {
|
||||
const { fixed } = useThemeColors();
|
||||
const [isTrainView, setIsTrainView] = useState(false);
|
||||
//トレインビュー表示対象(特急、マリン)かを判定
|
||||
useEffect(() => {
|
||||
@@ -21,7 +22,6 @@ export const TrainViewIcon: FC<Props> = ({ data, navigate, from }) => {
|
||||
);
|
||||
}, [data.limited]);
|
||||
const onPressTrainView = () => {
|
||||
LayoutAnimation.easeInEaseOut(); //setLoadingDelayData(true);
|
||||
navigate("trainbase", {
|
||||
info: "train.html?tn=" + data.trainNum,
|
||||
from,
|
||||
@@ -31,7 +31,7 @@ export const TrainViewIcon: FC<Props> = ({ data, navigate, from }) => {
|
||||
return isTrainView ? (
|
||||
<Ionicons
|
||||
name="subway"
|
||||
color="white"
|
||||
color={fixed.textOnPrimary}
|
||||
size={30}
|
||||
style={{ margin: 5 }}
|
||||
onPress={onPressTrainView}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
View,
|
||||
LayoutAnimation,
|
||||
ScrollView,
|
||||
Linking,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
Platform,
|
||||
BackHandler,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import dayjs from "dayjs";
|
||||
@@ -21,13 +21,19 @@ import ViewShot from "react-native-view-shot";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import { useTrainDelayData } from "../../stateBox/useTrainDelayData";
|
||||
import { BottomButtons } from "./JRSTraInfo/BottomButtons";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useResponsive } from "@/lib/responsive";
|
||||
import { useSheetMaxHeight } from "./useSheetMaxHeight";
|
||||
export const JRSTraInfo = () => {
|
||||
const { getTime, delayData, loadingDelayData, setLoadingDelayData } =
|
||||
useTrainDelayData();
|
||||
const { colors, fixed } = useThemeColors();
|
||||
const { fontScale, moderateScale, verticalScale } = useResponsive();
|
||||
const timeData = dayjs(getTime).format("HH:mm");
|
||||
const actionSheetRef = useRef(null);
|
||||
const scrollHandlers = useScrollHandlers("scrollview-1", actionSheetRef);
|
||||
const scrollHandlers = useScrollHandlers();
|
||||
const insets = useSafeAreaInsets();
|
||||
const maxHeight = useSheetMaxHeight();
|
||||
const viewShot = useRef(null);
|
||||
|
||||
const onCapture = async () => {
|
||||
@@ -37,7 +43,7 @@ export const JRSTraInfo = () => {
|
||||
if (ok) {
|
||||
await Sharing.shareAsync(
|
||||
"file://" + url,
|
||||
(options = { mimeType: "image/jpeg", dialogTitle: "Share this image" })
|
||||
{ mimeType: "image/jpeg", dialogTitle: "Share this image" }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -47,31 +53,33 @@ export const JRSTraInfo = () => {
|
||||
gestureEnabled
|
||||
CustomHeaderComponent={<></>}
|
||||
ref={actionSheetRef}
|
||||
isModal={Platform.OS == "ios"}
|
||||
containerStyle={
|
||||
Platform.OS == "android" ? { paddingBottom: insets.bottom } : {}
|
||||
}
|
||||
isModal={Platform.OS === "ios" && !Platform.isPad}
|
||||
containerStyle={{
|
||||
...(Platform.OS == "android" ? { paddingBottom: insets.bottom } : {}),
|
||||
...(maxHeight != null ? { maxHeight } : {}),
|
||||
}}
|
||||
useBottomSafeAreaPadding={Platform.OS == "android"}
|
||||
>
|
||||
<Handler />
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#0099CC",
|
||||
borderTopRadius: 5,
|
||||
borderColor: "dark",
|
||||
backgroundColor: fixed.primary,
|
||||
borderTopLeftRadius: 5,
|
||||
borderTopRightRadius: 5,
|
||||
borderColor: colors.border,
|
||||
borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<ViewShot ref={viewShot} options={{ format: "jpg" }}>
|
||||
<View
|
||||
style={{ height: 26, width: "100%", backgroundColor: "#0099CC" }}
|
||||
style={{ height: verticalScale(26), width: "100%", backgroundColor: fixed.primary }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 6,
|
||||
width: 45,
|
||||
height: verticalScale(6),
|
||||
width: moderateScale(45),
|
||||
borderRadius: 100,
|
||||
backgroundColor: "#f0f0f0",
|
||||
backgroundColor: colors.borderLight,
|
||||
marginVertical: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
@@ -82,31 +90,31 @@ export const JRSTraInfo = () => {
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#0099CC",
|
||||
backgroundColor: fixed.primary,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
|
||||
<Text style={{ fontSize: fontScale(30), fontWeight: "bold", color: fixed.textOnPrimary }}>
|
||||
列車遅延速報EX
|
||||
</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
|
||||
<Text style={{ fontSize: fontScale(30), fontWeight: "bold", color: fixed.textOnPrimary }}>
|
||||
{timeData}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name="reload"
|
||||
color="white"
|
||||
size={30}
|
||||
color={fixed.textOnPrimary}
|
||||
size={moderateScale(30)}
|
||||
style={{ margin: 5 }}
|
||||
onPress={() => {
|
||||
LayoutAnimation.easeInEaseOut(), setLoadingDelayData(true);
|
||||
setLoadingDelayData(true);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView {...scrollHandlers}>
|
||||
<ScrollView {...scrollHandlers as any}>
|
||||
<View
|
||||
style={{
|
||||
padding: 10,
|
||||
backgroundColor: "white",
|
||||
backgroundColor: colors.surface,
|
||||
}}
|
||||
>
|
||||
{loadingDelayData ? (
|
||||
@@ -114,7 +122,7 @@ export const JRSTraInfo = () => {
|
||||
<LottieView
|
||||
autoPlay
|
||||
loop
|
||||
style={{ width: 150, height: 150, backgroundColor: "#fff" }}
|
||||
style={{ width: 150, height: 150, backgroundColor: colors.surface }}
|
||||
source={require("../../assets/51690-loading-diamonds.json")}
|
||||
/>
|
||||
</View>
|
||||
@@ -123,26 +131,26 @@ export const JRSTraInfo = () => {
|
||||
let data = d.split(" ");
|
||||
return (
|
||||
<View style={{ flexDirection: "row" }} key={data[1]}>
|
||||
<Text style={{ flex: 15, fontSize: 18 }}>
|
||||
<Text style={{ flex: 15, fontSize: fontScale(18), color: colors.text }}>
|
||||
{data[0].replace("\n", "")}
|
||||
</Text>
|
||||
<Text style={{ flex: 5, fontSize: 18 }}>{data[1]}</Text>
|
||||
<Text style={{ flex: 6, fontSize: 18 }}>{data[3]}</Text>
|
||||
<Text style={{ flex: 5, fontSize: fontScale(18), color: colors.text }}>{data[1]}</Text>
|
||||
<Text style={{ flex: 6, fontSize: fontScale(18), color: colors.text }}>{data[3]}</Text>
|
||||
</View>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Text>現在、5分以上の遅れはありません。</Text>
|
||||
<Text style={{ color: colors.text }}>現在、5分以上の遅れはありません。</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={{ padding: 10, backgroundColor: "#0099CC" }}>
|
||||
<View style={{ padding: 10, backgroundColor: fixed.primary }}>
|
||||
<Text
|
||||
style={{ fontSize: 20, fontWeight: "bold", color: "white" }}
|
||||
style={{ fontSize: fontScale(20), fontWeight: "bold", color: fixed.textOnPrimary }}
|
||||
>
|
||||
列車遅延情報EXについて
|
||||
</Text>
|
||||
<Text style={{ color: "white" }}>
|
||||
<Text style={{ color: fixed.textOnPrimary }}>
|
||||
列車遅延情報をJR四国公式列車運行情報より5分毎に取得します。Twitterにて投稿している内容と同一のものとなります。
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -8,23 +8,27 @@ import {
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
|
||||
const styles: StyleProp<ViewStyle> = {
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
borderColor: "white",
|
||||
borderWidth: 1,
|
||||
margin: 10,
|
||||
borderRadius: 5,
|
||||
alignItems: "center",
|
||||
backgroundColor: "#0099CC",
|
||||
};
|
||||
export const BottomButtons: FC<{ onCapture: () => void }> = ({ onCapture }) => {
|
||||
const { fixed } = useThemeColors();
|
||||
|
||||
const styles: StyleProp<ViewStyle> = {
|
||||
padding: 10,
|
||||
flexDirection: "row",
|
||||
borderColor: fixed.textOnPrimary,
|
||||
borderWidth: 1,
|
||||
margin: 10,
|
||||
borderRadius: 5,
|
||||
alignItems: "center",
|
||||
backgroundColor: fixed.primary,
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
padding: 10,
|
||||
backgroundColor: "#0099CC",
|
||||
backgroundColor: fixed.primary,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
@@ -33,16 +37,16 @@ export const BottomButtons: FC<{ onCapture: () => void }> = ({ onCapture }) => {
|
||||
style={{ ...styles, flex: 1 }}
|
||||
onPress={() => Linking.openURL("https://mstdn.y-zu.org/@JRSTraInfoEX")}
|
||||
>
|
||||
<MaterialCommunityIcons name="mastodon" color="white" size={30} />
|
||||
<MaterialCommunityIcons name="mastodon" color={fixed.textOnPrimary} size={30} />
|
||||
<View style={{ flex: 1 }} />
|
||||
<Text style={{ fontSize: 25, fontWeight: "bold", color: "white" }}>
|
||||
<Text style={{ fontSize: 25, fontWeight: "bold", color: fixed.textOnPrimary }}>
|
||||
MastodonBOT
|
||||
</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles} onPress={onCapture}>
|
||||
<MaterialCommunityIcons name="share-variant" color="white" size={30} />
|
||||
<MaterialCommunityIcons name="share-variant" color={fixed.textOnPrimary} size={30} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -3,44 +3,48 @@ 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 { MaterialCommunityIcons, Foundation } from "@expo/vector-icons";
|
||||
import { Linking } from "react-native";
|
||||
import TouchableScale from "react-native-touchable-scale";
|
||||
import { useThemeColors } from "@/lib/theme";
|
||||
import { useSheetMaxHeight } from "./useSheetMaxHeight";
|
||||
export const Social = () => {
|
||||
const actionSheetRef = useRef(null);
|
||||
const insets = useSafeAreaInsets();
|
||||
const { colors, fixed } = useThemeColors();
|
||||
const maxHeight = useSheetMaxHeight();
|
||||
|
||||
return (
|
||||
<ActionSheet
|
||||
gestureEnabled
|
||||
CustomHeaderComponent={<></>}
|
||||
ref={actionSheetRef}
|
||||
isModal={Platform.OS == "ios"}
|
||||
containerStyle={
|
||||
Platform.OS == "android"
|
||||
? {
|
||||
paddingBottom: insets.bottom,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
isModal={Platform.OS === "ios" && !Platform.isPad}
|
||||
containerStyle={{
|
||||
...(Platform.OS == "android"
|
||||
? { paddingBottom: insets.bottom }
|
||||
: {}),
|
||||
...(maxHeight != null ? { maxHeight } : {}),
|
||||
}}
|
||||
useBottomSafeAreaPadding={Platform.OS == "android"}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#0099CC",
|
||||
borderTopRadius: 5,
|
||||
backgroundColor: fixed.primary,
|
||||
borderTopLeftRadius: 5,
|
||||
borderTopRightRadius: 5,
|
||||
borderColor: "dark",
|
||||
borderWidth: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<View style={{ height: 26, width: "100%", backgroundColor: "#0099CC" }}>
|
||||
<View style={{ height: 26, width: "100%", backgroundColor: fixed.primary }}>
|
||||
<View
|
||||
style={{
|
||||
height: 6,
|
||||
width: 45,
|
||||
borderRadius: 100,
|
||||
backgroundColor: "#f0f0f0",
|
||||
backgroundColor: colors.borderLight,
|
||||
marginVertical: 10,
|
||||
alignSelf: "center",
|
||||
}}
|
||||
@@ -53,17 +57,39 @@ export const Social = () => {
|
||||
<MaterialCommunityIcons
|
||||
name="web"
|
||||
style={{ padding: 5 }}
|
||||
color="white"
|
||||
color={fixed.textOnPrimary}
|
||||
size={30}
|
||||
/>
|
||||
<Text style={{ fontSize: 30, fontWeight: "bold", color: "white" }}>
|
||||
<Text style={{ fontSize: 30, fontWeight: "bold", color: fixed.textOnPrimary }}>
|
||||
JR四国公式SNS一族
|
||||
</Text>
|
||||
</View>
|
||||
<ListItem
|
||||
bottomDivider
|
||||
onPress={() => Linking.openURL("tel:0570-00-4592")}
|
||||
// @ts-ignore: TouchableScale props passed through Component
|
||||
friction={90}
|
||||
tension={100}
|
||||
activeScale={0.95}
|
||||
Component={TouchableScale}
|
||||
containerStyle={{ backgroundColor: "#8AE234" }}
|
||||
>
|
||||
<Foundation name="telephone" color={fixed.textOnPrimary} size={30} />
|
||||
<ListItem.Content>
|
||||
<ListItem.Title style={{ color: fixed.textOnPrimary, fontWeight: "bold" }}>
|
||||
JR四国案内センター
|
||||
</ListItem.Title>
|
||||
<ListItem.Subtitle style={{ color: fixed.textOnPrimary }}>
|
||||
0570-00-4592(8:00〜20:00 年中無休)
|
||||
</ListItem.Subtitle>
|
||||
</ListItem.Content>
|
||||
{/* @ts-ignore: ListItem.Chevron doesn't expose color prop */}
|
||||
<ListItem.Chevron color={"white"} />
|
||||
</ListItem>
|
||||
<ScrollView
|
||||
style={{
|
||||
padding: 10,
|
||||
backgroundColor: "white",
|
||||
backgroundColor: colors.surface,
|
||||
borderBottomLeftRadius: 10,
|
||||
borderBottomRightRadius: 10,
|
||||
flex:1
|
||||
@@ -128,13 +154,15 @@ export const Social = () => {
|
||||
bottomDivider
|
||||
onPress={() => Linking.openURL(d.url)}
|
||||
key={d.url}
|
||||
// @ts-ignore: TouchableScale props passed through Component
|
||||
friction={90} //
|
||||
tension={100} // These props are passed to the parent component (here TouchableScale)
|
||||
activeScale={0.95} //
|
||||
Component={TouchableScale}
|
||||
containerStyle={{ backgroundColor: colors.surface }}
|
||||
>
|
||||
<ListItem.Content>
|
||||
<ListItem.Title>{d.name}</ListItem.Title>
|
||||
<ListItem.Title style={{ color: colors.text }}>{d.name}</ListItem.Title>
|
||||
</ListItem.Content>
|
||||
<ListItem.Chevron />
|
||||
</ListItem>
|
||||
|
||||