121 Commits

Author SHA1 Message Date
harukin-expo-dev-env
7d454cf9d4 Merge commit '09c00202247c0c97f1d8c324c2cc49214eee1393' 2026-02-07 09:04:47 +00:00
harukin-expo-dev-env
699e3039b9 Merge commit '935b63f6cee9d84530a5e7f6e8f55bcc90f8a168' 2026-02-01 13:18:49 +00:00
harukin-expo-dev-env
5af3164f21 Merge commit 'ef81c1f4cdc325f66a55b9f06212190dbec5be24' 2025-12-31 16:08:13 +00:00
harukin-expo-dev-env
cd585b8206 Merge commit 'bb1ee2666e5c91819bc3330128a9636b5e2ad753' 2025-12-31 09:52:36 +00:00
harukin-expo-dev-env
927c73567a Merge commit '0bf345ff6ac492e66da91e71db0118adb58cb28a' 2025-12-20 10:14:56 +00:00
harukin-expo-dev-env
09170c6636 Merge commit '24f32335c48eda61d312eceaf6bdd1e442c63f60' 2025-12-12 19:27:28 +00:00
harukin-expo-dev-env
8705f69725 Merge commit '2975e9094e17963b726753d954ba347226af98e2' 2025-12-05 15:25:11 +00:00
harukin-expo-dev-env
9562e04ffe Merge commit '6f74f5dfa26369dc4ce15435eccb9af51b5842e3' 2025-11-06 01:41:13 +00:00
harukin-expo-dev-env
2ffb47b60c Merge commit '09f700fb667a9dc0b05488e05c3d9cd3c31ffaae' 2025-11-06 01:33:37 +00:00
harukin-expo-dev-env
1208a78831 Merge commit 'efd9b77cadfbff4205e17388e7b2a6cad637ba9f' 2025-09-29 18:43:34 +00:00
harukin-expo-dev-env
493ef92bd6 Merge commit 'dfaf5b05b93f8fdcddb17da2a4981faffc28e0ac' 2025-09-29 06:08:16 +00:00
harukin-expo-dev-env
a51ffe4a82 Merge commit 'fa1562f870e8f5fa26db931785dd297eeb834c56' 2025-09-28 19:50:05 +00:00
harukin-expo-dev-env
0ea25f1e97 Merge commit '19f9b58497df949ff20cbad00a344118ac5740cd' 2025-09-27 17:51:11 +00:00
harukin-expo-dev-env
16bd0fe192 Merge commit '59e7ba5290bd4f175c9dafbc0dd664b75417bf2f' 2025-09-25 03:00:42 +00:00
harukin-expo-dev-env
d75a495b19 Merge commit '0d9c1cdb186a2cecc2e0aec441d9af9ab6ff56a6' 2025-09-11 05:55:26 +00:00
harukin-expo-dev-env
a346a1477c Merge commit 'bc4cb450a3dfb0005d7e815019b6317953478a9f' 2025-09-09 16:16:57 +00:00
harukin-expo-dev-env
c93ca94a61 Merge commit '9b4c0735b0475659aa7fe1f688b8daef13b714f1' 2025-08-27 01:20:09 +00:00
harukin-expo-dev-env
4480c161d1 Merge commit '087f6c882968bdef782bfe2aa0005a67fe436c7c' 2025-08-24 11:30:06 +00:00
harukin-expo-dev-env
deb9b40949 Merge commit 'ddaad38ccc2d23121f7fb6e3678417e7ac9ed593' 2025-08-24 04:49:29 +00:00
harukin-expo-dev-env
fced009607 Merge commit 'eabb2499fa43ba6a5adb3ae71e18067d86d63e35' 2025-08-19 19:19:24 +00:00
harukin-expo-dev-env
0890c1b9ff Merge commit '7bbb5b972fdb2eb429e5f71d6dd30e279b47ad10' 2025-08-13 12:50:56 +00:00
harukin-expo-dev-env
4b3816940b Merge commit 'ff4eb2c95f72581771b09ea820e9413540684e7b' 2025-08-08 10:54:00 +00:00
harukin-expo-dev-env
9ea23d6eba Merge commit '3dbaa6bfbf2bf18d01464c5e367c95593250dc85' 2025-07-27 04:12:01 +00:00
harukin-expo-dev-env
31ea303b88 Merge commit '9b266c15f80301e0b7a47cfc702c52beec36f4c4' 2025-07-27 03:54:24 +00:00
harukin-expo-dev-env
7716cb516c Merge commit 'dc7cc555c990fd4a2cd5cf47089339c887eecc75' 2025-07-18 15:42:20 +00:00
harukin-expo-dev-env
70976c0554 Merge commit '8a7285bb20c76d6e99cafd96193957829906f9ac' 2025-07-08 16:44:25 +00:00
harukin-expo-dev-env
5616c7ed96 Merge commit '8212148fb298dc678b811aadf0344731b43e8cf7' 2025-05-04 12:56:37 +00:00
harukin-expo-dev-env
13580a57d4 Merge commit 'bd2248e1a943e574ccc44892122cd9da4df14b68' 2025-04-20 17:22:59 +00:00
harukin-expo-dev-env
c9481fb0c2 Merge commit 'b64138178c4ca99bfcbb4f115403a18674ce8136' 2025-04-14 19:10:10 +00:00
harukin-expo-dev-env
5cda45740c Merge commit '67f40b55c19ff2e94b1168865486bcde7d457cab' 2025-04-10 16:38:21 +00:00
harukin-expo-dev-env
84d1305796 Merge commit 'fdefbc82bd39c8cba29911273db2f96fa39c0e3f' 2025-04-10 15:55:13 +00:00
harukin-expo-dev-env
336e3510fa Merge commit 'af8f3333cd68ea507675ad02ec59ccf2f7959e00' 2025-03-30 02:52:30 +00:00
harukin-expo-dev-env
25780e1664 Merge commit '0a364021ce2fed98257e80cc780ac4020cecb9c1' 2025-03-25 07:27:06 +00:00
harukin-expo-dev-env
e6fab84393 Merge commit 'de2b94292dc97df6e290e91976d01cea24c98ced' 2025-03-22 12:33:34 +00:00
harukin-expo-dev-env
390acdeab7 Merge commit '8c75e06ac18e33588b5bf200d7fccf05d3c34c22' 2025-03-22 11:34:25 +00:00
harukin-expo-dev-env
ff7a5624e6 Merge commit '45500e7a4ae614debd93404339958465db88f71d' 2025-03-19 15:49:36 +00:00
harukin-expo-dev-env
9b79f224d3 Merge commit '2c5023568a0442679b476bd84651eca3fcc90ee1' 2025-03-16 05:56:14 +00:00
harukin-expo-dev-env
2f76e3776f Merge commit '8a48bc48e6eb82921fcda3a84e07db2eca51e614' 2025-03-15 04:23:16 +00:00
harukin-expo-dev-env
026da47d82 Merge commit '23fb2d715a461cb0ebfca1e1944757febd315fec' 2025-03-14 18:29:20 +00:00
harukin-expo-dev-env
29be052bf6 Merge commit 'ab883827e13b5c99556857395ac950ee00c53b83' 2025-03-11 05:26:18 +00:00
harukin-expo-dev-env
2fb7d97d33 Merge commit '31618aab496a7979a39e534d023cb69103b06721' 2025-03-10 15:51:25 +00:00
harukin-expo-dev-env
76ebf8055b Merge commit '26cf84705abd4c9a69ac56fcade483a373089bf8' 2025-03-06 03:52:42 +00:00
harukin-expo-dev-env
7b572cd657 Merge commit 'fa882223d49fec390177131371b99a3101976759' 2025-03-05 11:51:51 +00:00
harukin-expo-dev-env
61fa0f8484 Merge commit 'ede2884c4a7b8a508cfd9f4f7cc87da4efe3db08' 2025-03-04 16:17:50 +00:00
harukin-expo-dev-env
935aaf2610 Merge commit '098cae50533368eec9cfdb933900c503ae46e850' 2025-03-04 15:01:08 +00:00
harukin-expo-dev-env
8ec53d6e06 Merge commit '0aaf171477659e632a23aeb6af39e83d5acf70c2' 2025-02-09 03:21:16 +00:00
harukin-expo-dev-env
e8b1a21a3b Merge commit '4a01c529b47cd85420a4c1feeac87164730dee94' 2025-01-15 12:38:58 +00:00
harukin-expo-dev-env
e6b89842a3 Merge commit '2776f17681ac2bf76a70c268c4db8921c5888a4f' 2025-01-14 07:42:06 +00:00
harukin-expo-dev-env
1fc5220405 Merge commit '58d3eae1d7797a74febc752f4185337a6eb95dad' 2025-01-09 09:56:21 +00:00
harukin-expo-dev-env
5489406578 Merge commit 'a7c4f689bc0762a63b662901f943a62827b765eb' 2024-11-22 05:40:18 +00:00
harukin-expo-dev-env
2b217e98c7 Merge commit 'd8108e2c9c60a60ba7f933af12af996b3cc0491c' 2024-11-22 05:19:00 +00:00
harukin-expo-dev-env
2c2e61a2fa Merge commit '6d3e6623a0774cb489efa3b81335db319fad2b2d' 2024-10-31 12:27:16 +00:00
harukin-expo-dev-env
c222b303df Merge commit '1346909bb74c80bedf1841aefda66a67adb98443' 2024-09-30 13:00:12 +00:00
harukin-expo-dev-env
8f8c095ecd Merge commit 'ab2a18b562c5fe18087f3140449fc7764b969df6' 2024-09-28 20:45:33 +00:00
harukin-expo-dev-env
e31e84ff34 Merge commit '9b91c4a50e1df914f66c6c5b33007caf4b0c9c32' 2024-09-20 15:18:49 +00:00
harukin-expo-dev-env
2c2b355a3e Merge commit '63209ac88878f70d0896dca429277d09ef1bf492' 2024-09-16 16:34:11 +00:00
harukin-expo-dev-env
5461087ca0 Merge commit 'c9a90809c6b178d5b85d51d47521d2541bbed7f6' 2024-09-16 16:31:31 +00:00
harukin-expo-dev-env
57c7285b6e Merge commit '628d2a42c94801ea42c939dd702ebd448176e62f' 2024-09-16 16:10:05 +00:00
harukin-expo-dev-env
5c134c95cc Merge commit '801b3dc3b0dde2f8a46962cfbca1599fb31cb6a4' 2024-09-02 02:29:04 +00:00
harukin-expo-dev-env
8781653fe8 Merge commit '9b4b4bd0d6572d0d0ffdb35ec72559bf035d07a3' 2024-06-05 10:27:22 +00:00
harukin-expo-dev-env
67ccc37c17 Merge commit '35e05e92f50ebca5d4666b9a008915b7f437f86c' 2024-06-05 09:16:05 +00:00
harukin-expo-dev-env
92caab03f5 Merge commit '52c2da333a97234bb27a25baf8af8479d53c3422' 2024-06-03 12:40:26 +00:00
harukin-expo-dev-env
dd3a57b3ae Merge commit 'b9b983a177d37db717b551f8b18d5f6c87aa0e23' 2024-06-03 12:20:23 +00:00
harukin-expo-dev-env
8df32b9c1d Merge commit '7eea78027649dec4dd7492efd8edfb0e61df5eea' 2024-04-28 10:33:20 +00:00
harukin-expo-dev-env
4b901d5015 Merge commit '5d711d37550d288142e3e768a2a0f42a7279d434' 2024-04-18 03:32:42 +00:00
harukin-expo-dev-env
fc5c62685a Merge commit '85e2ad329d89e57be4c094067de48525fe7673e3' 2024-04-18 03:21:26 +00:00
harukin-expo-dev-env
fbc98b2ff7 Merge commit '05167c810a8b727ed6527dbb07c0b9c1de53c171' 2024-04-06 17:19:41 +00:00
harukin-expo-dev-env
3502043176 Merge commit '0efab93a1451818339bfbed97545e6782eafcb9c' 2024-04-06 17:19:32 +00:00
harukin-expo-dev-env
2d0ad8d59e Merge commit 'dd7da102c43348a5420a4b616509d37c8d03a65f' 2024-03-27 15:52:25 +00:00
harukin-expo-dev-env
b5172df7a9 Merge commit '548d5d3747e6491ae421a716e85f7d60be4e41e4' 2024-03-27 15:02:35 +00:00
harukin-expo-dev-env
740d414d2d Merge commit '7e59b8c7bd7d01b35b5b0d6c5be93baed222f660' 2024-03-26 12:59:20 +00:00
harukin-expo-dev-env
8db3e6c218 Merge commit '736f9a65e92257237f347bbe2d02fe88f0e409d6' 2024-03-26 12:54:31 +00:00
harukin-expo-dev-env
fb35b01d2e Merge commit '720b627011a108979ce07a103aff2d728e68cab0' 2024-03-18 12:50:34 +00:00
harukin-expo-dev-env
5fa6b1f73e Merge commit '481ca0158d93b90af0306ed3d54786d61d6281e2' 2024-03-18 04:17:25 +00:00
harukin-expo-dev-env
c16f7401ea Merge commit '5864e821120ec726c6e96fbc8edc6a7172d5bbd7' 2024-03-18 04:04:16 +00:00
harukin-expo-dev-env
f260c5d2dd Merge commit 'a769ccb9512d8c246be82cb1f16673b13b39d4e6' 2024-03-15 18:04:11 +00:00
harukin-expo-dev-env
80e9f1a869 Merge commit 'd8ce2a7f8aac23e1001bbe16690d37c57d1c704f' 2024-03-13 13:27:05 +00:00
harukin-expo-dev-env
26096ba244 Merge commit '5806e2a259e03182d082fe454f4a1932ad9c5e53' 2024-03-13 13:10:22 +00:00
harukin-expo-dev-env
435a910ef9 Merge commit 'f2aed4b945d3e86381ebb964787fcb5aff35fdbd' 2024-03-12 16:47:26 +00:00
harukin-expo-dev-env
68b9236d65 Merge commit '7bd7d951048357c4c0b81d9048906275066e8526' 2024-03-12 16:36:37 +00:00
harukin-expo-dev-env
0148c12e08 Merge commit '75952f4edea5d59554322a5c611483a0052e4e97' 2024-03-12 14:37:09 +00:00
harukin-expo-dev-env
34dc62aee6 Merge commit 'c0cdad36837f27dee7c22930834272052d53d090' 2024-03-09 15:48:41 +00:00
harukin-OneMix4
54ae681f00 Merge commit '8a94b81052a4cf6b833bf298abcd89ed93a0ada2' 2024-01-27 01:37:29 +09:00
harukin-OneMix4
db40351fec Merge commit '431c4c9c0b426250adefbb42db7893233fabb55b' 2024-01-27 01:04:19 +09:00
harukin-OneMix4
2dd8e62f85 Merge commit '485102b5917eaa819c615dbaf47ee27f81a5751f' 2024-01-12 14:04:37 +09:00
harukin-OneMix4
0937fbb619 Merge commit '65080e68f2197f31b8a4ed4bf94ad325aa77e923' 2024-01-09 18:31:30 +09:00
harukin-OneMix4
32edab3289 Merge commit '87129c6815233ea6e42575b83a26c0a2c34a6e5c' 2024-01-09 18:21:41 +09:00
harukin-OneMix4
fa96e68770 Merge commit 'b54f2f6f7e1478cee3f1f3cacf2bc3949d45583f' 2024-01-04 02:38:22 +09:00
harukin-OneMix4
1681be4437 Merge commit '0e06b6636862fa4728d36bb9b934490087cadf02' 2024-01-04 00:47:55 +09:00
harukin-OneMix4
7f96c44e88 Merge commit '0f0d69b22d4e8b3aea9bec06855d38751881055a' 2024-01-03 01:35:33 +09:00
harukin-OneMix4
7d485c466c Merge commit '29c84bcc1ce7bd1d2512b65ef812a313507d8650' 2024-01-03 01:07:31 +09:00
harukin-OneMix4
7869cbee6d Merge commit 'a4e85ff2e6bccb35ecf04f6bdf51ac193585b765' 2023-12-29 03:50:28 +09:00
harukin-OneMix4
c2ad681891 Merge commit '57459d975bbe3a1b4e145331b57a638b2c2fead5' 2023-12-29 02:09:44 +09:00
harukin-OneMix4
163f9ead18 Merge commit 'b23b59eab349139656b2d32c4b19f744a937aa59' 2023-12-25 17:09:41 +09:00
harukin-OneMix4
611fc9953f Merge commit 'e38f550b94a6c89fd2840ff95af392e8e9f49589' 2023-12-25 15:46:35 +09:00
harukin-OneMix4
fd95e99874 Merge commit '9b70843e9c6ed45ebce609952b0202f5ebf877d7' 2023-12-25 05:47:23 +09:00
harukin-OneMix4
f5d0f993db update three month update 2023-12-13 15:56:40 +09:00
harukin-OneMix4
b7e763d265 update three month update 2023-09-17 15:44:19 +09:00
harukin-OneMix4
0afa6f402b 4.5.4.1 2023-07-16 23:00:26 +09:00
harukin-OneMix4
eacce53775 Merge commit '84d36b88cc71ed591c9a50f086ab38cd08177758' 2023-07-16 23:00:05 +09:00
harukin-OneMix4
47b1eb325e なんかマージミスった? 2023-07-16 19:40:44 +09:00
harukin-OneMix4
a145e2c24f Merge commit '711f0bbe9f00200c02fb70757d7c0ca2d0060ed4' 2023-07-16 19:36:57 +09:00
harukin-OneMix4
862742cd3e update build number 2023-06-19 14:03:37 +09:00
harukin-OneMix4
d5f2d367dd Merge commit 'a3e8e3cbbe13e4fc137937493a91708b1dff7cae' 2023-06-18 21:05:04 +09:00
harukin-OneMix4
b1f72f18b5 Merge commit '81f394d6ca2ae418c89452acabe11196e4b7e940' 2023-05-10 17:19:18 +09:00
harukin-OneMix4
d4d86fb7eb 4.5.3 2023-05-10 17:13:10 +09:00
harukin-OneMix4
056a059a68 Merge commit 'bb115afe358eb7f2fb724beb1da13b2be24780b4' 2023-05-10 17:11:56 +09:00
harukin-DeskMini
12676d59cf 4.5.2リリース(本開放/ニュース更新) 2023-03-30 03:28:34 +09:00
harukin-DeskMini
bca2b300d3 4.5.2リリース(封印開放) 2023-03-30 03:25:28 +09:00
harukin-DeskMini
406808ee58 Merge commit '47d84777e2a26235d5442a68ae3b6bcb07f580cb' 2023-03-30 03:21:25 +09:00
harukin-DeskMini
a8aadb7f08 4.5.1 2023-03-27 07:01:47 +09:00
harukin-DeskMini
b810405573 Merge commit '65dd6dd0fdd41f06cfb686d578f9d604185510d3' 2023-03-27 07:01:20 +09:00
harukin-DeskMini
6f62418426 Merge commit '9eca67901906b8852f1f1f2dfa814ed658514b4b' 2023-03-27 01:06:04 +09:00
harukin-DeskMini
88621dd1c8 Merge commit '98b22db4dbea611907c4d7a6a60b7323dbec7a76' 2023-03-27 01:02:57 +09:00
harukin-DeskMini
12d9c1f49d 封印忘れ物 2023-03-26 21:45:04 +09:00
harukin-DeskMini
30c2b8a2f9 4.5.0-リリース用一部封印 2023-03-26 21:42:49 +09:00
harukin-DeskMini
8bc7069c4e Merge commit '816d96d37be6537c9ebcf0be30c74fd154b80dc5' 2023-03-26 21:09:24 +09:00
harukin-OneMix4
737cd25539 Merge commit '64538e33f317c6fe395859673922f63c2e3b0514' 2023-03-02 17:37:40 +09:00
harukin-DeskMini
2c50e5af67 Merge commit 'e93fe7095ef717bd0ddebeb18f28038a374d83f6' 2023-02-01 17:16:10 +09:00
harukin-DeskMini
ea677b4da5 Merge commit '40fb55c8cf5462a40a499ddcc69cb5313e559f84' 2023-01-29 22:10:09 +09:00
harukin-DeskMini
4525443e39 Merge commit 'e35ab09002641b3813d2055ff8107bd8088c4629' 2023-01-29 09:11:58 +09:00
31 changed files with 935 additions and 2530 deletions

View File

@@ -1,5 +1,5 @@
import React, { CSSProperties } from "react";
import { Alert, BackHandler, View, ViewProps } from "react-native";
import { 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";
@@ -33,15 +33,7 @@ export default ({ route }) => {
source={{ uri }}
allowsBackForwardNavigationGestures
ref={webViewRef}
onNavigationStateChange={(navState) => {
setCanGoBack(navState.canGoBack);
if (navState.url === "https://unyohub.2pd.jp/integration/succeeded.php") {
goBack();
Alert.alert("鉄道運用HUBへの投稿完了", "運用HUBからのこのアプリへのデータ反映には暫く時間がかかりますので、しばらくお待ちください。", [
{ text: "完了" },
]);
}
}}
onNavigationStateChange={(navState) => setCanGoBack(navState.canGoBack)}
onMessage={(event) => {
const { data } = event.nativeEvent;
const { type } = JSON.parse(data);

View File

@@ -70,22 +70,22 @@ export const EachStopList: FC<props> = ({
string
]; // 阿波池田,発,6:21,1
let beforeSameStationData = null;
// 発(通常発・休発・休発編)の場合、前の着(通常着・休着・休着編)と統合する
if (se.includes("発")) {
// 運休系でない通常の発のみ、前の着を統合する
// 休編(非推奨)は発着が不明なため、次の発と統合する
if ((se.includes("発") && !se.includes("休")) || se === "休編") {
if (index > 0) {
const beforeData = array[index - 1].split(",") as [string, seTypes, string];
// 前が着(通常着でも休着でも)の場合は統合
if (beforeData[0] == station && beforeData[1].includes("着")) {
if (beforeData[0] == station) {
beforeSameStationData = beforeData;
}
}
}
let afterSameStationData = null;
// 着(通常着・休着・休着編)の場合、次の発(通常発・休発・休発編)と統合される(非表示)
if (se.includes("着")) {
// 運休系でない通常の着のみ、次の発と統合する
// 運休着(休着、休着編)は独立して表示する必要がある
if (se.includes("着") && !se.includes("休")) {
const afterData = array[index + 1]?.split(",") as [string, seTypes, string];
// 次が発(通常発でも休発でも)なら、この着を非表示にして次の発で両方表示
if (afterData && afterData[0] == station && afterData[1].includes("発")) {
if (afterData && afterData[0] == station) {
afterSameStationData = afterData;
return <></>;
}

View File

@@ -19,7 +19,6 @@ 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";
type Props = {
data: { trainNum: string; limited: string };
@@ -58,14 +57,6 @@ export const HeaderText: FC<Props> = ({
const { allCustomTrainData, getTodayOperationByTrainId } =
useAllTrainDiagram();
const { expoPushToken } = useNotification();
const { getUnyohubByTrainNumber, getUnyohubEntriesByTrainNumber, useUnyohub: unyohubEnabled } = useUnyohub();
// 追加ソースのON/OFFをここで管理将来ソースが増えたらここに足す
const additionalSources = {
unyohub: unyohubEnabled,
// exampleSource: exampleEnabled,
};
const hasAdditionalSources = Object.values(additionalSources).some(Boolean);
// 列車名、種別、フォントの取得
const [
@@ -77,7 +68,6 @@ export const HeaderText: FC<Props> = ({
priority,
uwasa,
trainInfoUrl,
directions,
] = useMemo(() => {
const {
type,
@@ -88,7 +78,6 @@ export const HeaderText: FC<Props> = ({
uwasa,
train_info_url,
to_data,
directions,
} = customTrainDataDetector(trainNum, allCustomTrainData);
const [typeString, fontAvailable, isOneMan] = getStringConfig(
type,
@@ -110,7 +99,6 @@ export const HeaderText: FC<Props> = ({
priority,
uwasa,
train_info_url,
directions,
];
case trainData[trainData.length - 1] === undefined:
return [
@@ -122,7 +110,6 @@ export const HeaderText: FC<Props> = ({
priority,
uwasa,
train_info_url,
directions,
];
case to_data && to_data !== "":
// 行先がある場合は、行先を取得
@@ -135,7 +122,6 @@ export const HeaderText: FC<Props> = ({
priority,
uwasa,
train_info_url,
directions,
];
default:
// 行先がある場合は、行先を取得
@@ -150,7 +136,6 @@ export const HeaderText: FC<Props> = ({
priority,
uwasa,
train_info_url,
directions,
];
}
}, [trainData]);
@@ -158,19 +143,6 @@ export const HeaderText: FC<Props> = ({
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 unyohubFormation = getUnyohubByTrainNumber(trainNum);
const unyohubEntries = getUnyohubEntriesByTrainNumber(trainNum);
const hasExtraInfo =
priority > 200 || todayOperation?.length > 0 || !!unyohubFormation;
return (
<View
style={{ padding: 10, flexDirection: "row", alignItems: "center" }}
@@ -183,7 +155,6 @@ export const HeaderText: FC<Props> = ({
navigate={navigate}
from={from}
todayOperation={todayOperation}
direction={iconTrainDirection}
/>
<TouchableOpacity
style={{
@@ -258,27 +229,15 @@ export const HeaderText: FC<Props> = ({
</TouchableOpacity>
<MaterialCommunityIcons
name="database"
color={hasExtraInfo ? "yellow" : "white"}
color={
priority > 200 || todayOperation?.length > 0 ? "yellow" : "white"
}
size={30}
style={{ margin: 5 }}
onPress={() => {
if (hasAdditionalSources) {
(SheetManager.show as any)("TrainDataSources", {
payload: {
trainNum,
unyohubEntries,
todayOperation,
navigate,
expoPushToken,
priority,
},
});
} 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");
}
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
navigate("generalWebView", { uri, useExitButton: false });
SheetManager.hide("EachTrainInfo");
}}
/>
</View>

View File

@@ -1,463 +0,0 @@
import React, { FC } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
Image,
} from "react-native";
import { MaterialCommunityIcons, Ionicons } from "@expo/vector-icons";
import { SheetManager } from "react-native-actions-sheet";
import type { NavigateFunction } from "@/types";
import type { OperationLogs } from "@/lib/CommonTypes";
import type { UnyohubData } from "@/types/unyohub";
type Props = {
trainNum: string;
unyohubEntries: UnyohubData[];
todayOperation: OperationLogs[];
navigate: NavigateFunction;
expoPushToken: string;
onClose: () => void;
};
/** 情報ソース別カードを並べて表示するパネル */
export const TrainSourcesPanel: FC<Props> = ({
trainNum,
unyohubEntries,
todayOperation,
navigate,
expoPushToken,
onClose,
}) => {
return (
<View style={styles.container}>
{/* ヘッダー */}
<View style={styles.panelHeader}>
<Text style={styles.panelHeaderText}></Text>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<Ionicons name="close" size={20} color="#0099CC" />
</TouchableOpacity>
</View>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
{/* ──────────────────────────────────────── */}
{/* JR四国データベース常に表示 */}
{/* ──────────────────────────────────────── */}
<SourceCard
iconName="database"
iconColor="#0099CC"
tag="公式"
tagColor="#0099CC"
title="JR四国データベース"
description="コミュニティが管理する列車データ。編成・運用の詳細を確認・編集できます。"
available
onPress={() => {
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`;
navigate("generalWebView", { uri, useExitButton: false });
SheetManager.hide("EachTrainInfo");
onClose();
}}
/>
{/* ──────────────────────────────────────── */}
{/* 列車運用hub */}
{/* ──────────────────────────────────────── */}
{unyohubEntries.length > 0 ? (
<>
<SectionLabel label="列車運用hub" icon="train" />
{unyohubEntries.map((entry, i) => (
<UnyohubCard key={`unyo-${i}`} entry={entry} trainNum={trainNum} navigate={navigate} onClose={onClose} />
))}
</>
) : (
<SourceCard
iconName="train"
iconColor="#aaa"
tag="外部"
tagColor="#aaa"
title="列車運用hub"
description="この列車の運用データはありません。"
available={false}
/>
)}
{/* ──────────────────────────────────────── */}
{/* 今日の運用情報 */}
{/* ──────────────────────────────────────── */}
{todayOperation.length > 0 ? (
<>
<SectionLabel label="今日の運用情報" icon="history" />
{todayOperation.map((op, i) => (
<OperationCard
key={`op-${i}`}
op={op}
index={i}
navigate={navigate}
onClose={onClose}
/>
))}
</>
) : (
<SourceCard
iconName="history"
iconColor="#aaa"
tag="記録"
tagColor="#aaa"
title="今日の運用情報"
description="本日の運用記録はありません。"
available={false}
/>
)}
</ScrollView>
</View>
);
};
/* ------------------------------------------------------------------ */
/* サブコンポーネント */
/* ------------------------------------------------------------------ */
/** セクションラベル */
const SectionLabel: FC<{ label: string; icon: string }> = ({ label, icon }) => (
<View style={styles.sectionLabel}>
<MaterialCommunityIcons name={icon as any} size={14} color="#888" />
<Text style={styles.sectionLabelText}>{label}</Text>
</View>
);
/** 汎用ソースカード */
const SourceCard: FC<{
iconName: string;
iconColor: string;
tag: string;
tagColor: string;
title: string;
description: string;
available: boolean;
onPress?: () => void;
}> = ({ iconName, iconColor, tag, tagColor, title, description, available, onPress }) => (
<TouchableOpacity
style={[styles.card, !available && styles.cardDisabled]}
onPress={available ? onPress : undefined}
activeOpacity={available ? 0.7 : 1}
>
<View style={[styles.cardIconWrap, { backgroundColor: iconColor + "22" }]}>
<MaterialCommunityIcons name={iconName as any} size={22} color={iconColor} />
</View>
<View style={styles.cardBody}>
<View style={styles.cardTitleRow}>
<Text style={[styles.cardTitle, !available && styles.cardTitleDisabled]}>{title}</Text>
<View style={[styles.tag, { backgroundColor: tagColor + "33" }]}>
<Text style={[styles.tagText, { color: tagColor }]}>{tag}</Text>
</View>
</View>
<Text style={styles.cardDesc}>{description}</Text>
</View>
{available && (
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
)}
</TouchableOpacity>
);
/** 列車運用hubカード1編成分 */
const UnyohubCard: FC<{
entry: UnyohubData;
trainNum: string;
navigate: NavigateFunction;
onClose: () => void;
}> = ({ entry, trainNum, navigate, onClose }) => {
const matchedTrain = entry.trains?.find((t) => t.train_number === trainNum);
const positionText = matchedTrain
? `位置: ${matchedTrain.position_forward}${matchedTrain.position_rear}両目`
: null;
const carInfo = `${entry.car_count}両 (${entry.min_car_count}${entry.max_car_count}両)`;
const timeInfo =
entry.starting_time && entry.ending_time
? `${entry.starting_time}${entry.ending_time}`
: null;
return (
<TouchableOpacity
style={styles.card}
activeOpacity={0.7}
onPress={() => {
// 編成番号+列番でページを開く将来的にunyohub公式ページへの対応も想定
const uri = `https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?formation=${encodeURIComponent(entry.formations)}&source=unyohub`;
navigate("generalWebView", { uri, useExitButton: true });
SheetManager.hide("EachTrainInfo");
onClose();
}}
>
{/* 編成色インジケーター */}
<View
style={[
styles.cardIconWrap,
{ backgroundColor: (entry.main_color || "#FF9800") + "33" },
]}
>
<MaterialCommunityIcons
name="train-car"
size={22}
color={entry.main_color || "#FF9800"}
/>
</View>
<View style={styles.cardBody}>
<View style={styles.cardTitleRow}>
<Text style={styles.cardTitle}>{entry.formations}</Text>
<View style={[styles.tag, { backgroundColor: "#FF980033" }]}>
<Text style={[styles.tagText, { color: "#FF9800" }]}>hub</Text>
</View>
{entry.from_beginner && (
<View style={[styles.tag, { backgroundColor: "#9C27B033" }]}>
<Text style={[styles.tagText, { color: "#9C27B0" }]}></Text>
</View>
)}
</View>
{/* 詳細行 */}
<View style={styles.unyoDetailRow}>
{positionText && (
<DetailChip icon="seat" text={positionText} />
)}
<DetailChip icon="train-car" text={carInfo} />
{timeInfo && <DetailChip icon="clock-outline" text={timeInfo} />}
{entry.starting_location && (
<DetailChip icon="map-marker" text={entry.starting_location} />
)}
</View>
{entry.comment && (
<Text style={styles.unyoComment}>{entry.comment}</Text>
)}
<View style={styles.postsRow}>
<MaterialCommunityIcons name="account-group" size={12} color="#888" />
<Text style={styles.postsText}>{entry.posts_count}稿</Text>
</View>
</View>
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
</TouchableOpacity>
);
};
/** 今日の運用情報カード */
const OperationCard: FC<{
op: OperationLogs;
index: number;
navigate: NavigateFunction;
onClose: () => void;
}> = ({ op, index, navigate, onClose }) => {
const hasUrl = !!op.vehicle_info_url;
const trainIds = [...(op.train_ids || []), ...(op.related_train_ids || [])];
return (
<TouchableOpacity
style={[styles.card, !hasUrl && styles.cardNoArrow]}
activeOpacity={hasUrl ? 0.7 : 1}
onPress={
hasUrl
? () => {
navigate("howto", { info: op.vehicle_info_url, goTo: "menu" });
SheetManager.hide("EachTrainInfo");
onClose();
}
: undefined
}
>
{/* 車両サムネイル or アイコン */}
{op.vehicle_img ? (
<Image
source={{ uri: op.vehicle_img }}
style={styles.vehicleThumb}
resizeMode="contain"
/>
) : (
<View style={[styles.cardIconWrap, { backgroundColor: "#4CAF5022" }]}>
<MaterialCommunityIcons name="train" size={22} color="#4CAF50" />
</View>
)}
<View style={styles.cardBody}>
<View style={styles.cardTitleRow}>
<Text style={styles.cardTitle}> #{index + 1}</Text>
<View style={[styles.tag, { backgroundColor: "#4CAF5033" }]}>
<Text style={[styles.tagText, { color: "#4CAF50" }]}></Text>
</View>
</View>
{trainIds.length > 0 && (
<Text style={styles.cardDesc} numberOfLines={1}>
: {trainIds.slice(0, 4).join(" / ")}
{trainIds.length > 4 ? " ..." : ""}
</Text>
)}
{op.date && (
<Text style={styles.cardDesc}>: {op.date}</Text>
)}
</View>
{hasUrl && (
<MaterialCommunityIcons name="chevron-right" size={18} color="#ccc" />
)}
</TouchableOpacity>
);
};
/** 小さい詳細チップ */
const DetailChip: FC<{ icon: string; text: string }> = ({ icon, text }) => (
<View style={styles.chip}>
<MaterialCommunityIcons name={icon as any} size={11} color="#666" />
<Text style={styles.chipText}>{text}</Text>
</View>
);
/* ------------------------------------------------------------------ */
/* スタイル */
/* ------------------------------------------------------------------ */
const styles = StyleSheet.create({
container: {
backgroundColor: "#fff",
borderTopWidth: 1,
borderTopColor: "#e0e0e0",
},
panelHeader: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 14,
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
panelHeaderText: {
flex: 1,
fontSize: 14,
fontWeight: "bold",
color: "#333",
},
closeButton: {
padding: 4,
},
scroll: {
maxHeight: 360,
},
scrollContent: {
paddingHorizontal: 12,
paddingVertical: 8,
paddingBottom: 16,
},
sectionLabel: {
flexDirection: "row",
alignItems: "center",
marginTop: 10,
marginBottom: 4,
gap: 4,
},
sectionLabelText: {
fontSize: 12,
color: "#888",
fontWeight: "600",
letterSpacing: 0.5,
},
card: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#f9f9f9",
borderRadius: 10,
marginBottom: 8,
padding: 10,
borderWidth: 1,
borderColor: "#eeeeee",
gap: 10,
},
cardDisabled: {
opacity: 0.5,
},
cardNoArrow: {
// no change needed
},
cardIconWrap: {
width: 40,
height: 40,
borderRadius: 10,
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
},
vehicleThumb: {
width: 50,
height: 40,
borderRadius: 6,
flexShrink: 0,
backgroundColor: "#eee",
},
cardBody: {
flex: 1,
gap: 3,
},
cardTitleRow: {
flexDirection: "row",
alignItems: "center",
gap: 6,
flexWrap: "wrap",
},
cardTitle: {
fontSize: 14,
fontWeight: "bold",
color: "#222",
},
cardTitleDisabled: {
color: "#aaa",
},
cardDesc: {
fontSize: 12,
color: "#666",
},
tag: {
borderRadius: 4,
paddingHorizontal: 5,
paddingVertical: 1,
},
tagText: {
fontSize: 10,
fontWeight: "bold",
},
unyoDetailRow: {
flexDirection: "row",
flexWrap: "wrap",
gap: 4,
marginTop: 2,
},
unyoComment: {
fontSize: 11,
color: "#888",
fontStyle: "italic",
marginTop: 2,
},
postsRow: {
flexDirection: "row",
alignItems: "center",
gap: 3,
marginTop: 2,
},
postsText: {
fontSize: 11,
color: "#888",
},
chip: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#f0f0f0",
borderRadius: 4,
paddingHorizontal: 5,
paddingVertical: 2,
gap: 3,
},
chipText: {
fontSize: 11,
color: "#555",
},
});

View File

@@ -17,18 +17,17 @@ 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, direction } = props;
const { data, navigate, from, todayOperation } = props;
const [anpanmanStatus, setAnpanmanStatus] = useState<apt>();
const { allCustomTrainData } = useAllTrainDiagram();
const [trainIconData, setTrainIcon] = useState<
{ vehicle_info_img: string;vehicle_info_right_img: string; vehicle_info_url: string }[]
{ vehicle_info_img: string; vehicle_info_url: string }[]
>([]);
useEffect(() => {
if (!data.trainNum) return;
@@ -80,12 +79,11 @@ export const TrainIconStatus: FC<Props> = (props) => {
})
.map((op) => ({
vehicle_info_img: op.vehicle_img || vehicle_info_img,
vehicle_info_right_img: op.vehicle_img_right || vehicle_info_img,
vehicle_info_url: op.vehicle_info_url,
})) || [];
setTrainIcon(returnData);
} else if (vehicle_info_img) {
setTrainIcon([{ vehicle_info_img, vehicle_info_right_img: vehicle_info_img, vehicle_info_url }]);
setTrainIcon([{ vehicle_info_img, vehicle_info_url }]);
}
switch (data.trainNum) {
@@ -115,6 +113,30 @@ export const TrainIconStatus: FC<Props> = (props) => {
}
});
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" });
}
});
break;
}
}, [data.trainNum, allCustomTrainData, todayOperation]);
const [move, setMove] = useState(true);
@@ -129,7 +151,7 @@ export const TrainIconStatus: FC<Props> = (props) => {
return (
<>
{trainIconData.map(
({ vehicle_info_img: trainIcon, vehicle_info_right_img: trainIconRight, vehicle_info_url: address }, index) => (
({ vehicle_info_img: trainIcon, vehicle_info_url: address }, index) => (
<TouchableOpacity
onPress={() => {
navigate("howto", {
@@ -142,7 +164,7 @@ export const TrainIconStatus: FC<Props> = (props) => {
>
{move ? (
<Image
source={{ uri: direction ? trainIcon : trainIconRight || trainIcon }}
source={{ uri: trainIcon }}
style={{
height: index > 0 ? 15 : 30,
width: index > 0 ? 12 : 24,

View File

@@ -1,390 +0,0 @@
import React, { FC } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
Platform,
} from "react-native";
import ActionSheet, { SheetManager } from "react-native-actions-sheet";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import type { NavigateFunction } from "@/types";
import type { OperationLogs } from "@/lib/CommonTypes";
import type { UnyohubData } from "@/types/unyohub";
export type TrainDataSourcesPayload = {
trainNum: string;
unyohubEntries: UnyohubData[];
todayOperation: OperationLogs[];
navigate: NavigateFunction;
expoPushToken: string;
/** customTrainDataDetector から取得した priority 値 */
priority: number;
};
export const TrainDataSources: FC<{ payload?: TrainDataSourcesPayload }> = ({
payload,
}) => {
if (!payload) return null;
const { trainNum, unyohubEntries, todayOperation, navigate, expoPushToken, priority } =
payload;
const close = () => SheetManager.hide("TrainDataSources");
const openWebView = (uri: string, useExitButton: boolean) => {
SheetManager.hide("EachTrainInfo");
SheetManager.hide("TrainDataSources");
navigate("generalWebView", { uri, useExitButton });
};
/* ── 各ソースの状態 ─────────────────────────────── */
const opCount = todayOperation.length;
const unyoCount = unyohubEntries.length;
const hasTrainInfo = priority > 200;
// 運用情報: 全エントリの unit_ids をフラットに収集・重複除去
const allUnitIds = [
...new Set(todayOperation.flatMap((op) => op.unit_ids ?? [])),
];
const unitIdsSub =
allUnitIds.length > 0
? allUnitIds.slice(0, 6).join("・") +
(allUnitIds.length > 6 ? `${allUnitIds.length - 6}` : "")
: opCount > 0
? todayOperation
.flatMap((op) => op.train_ids ?? [])
.slice(0, 4)
.join("・") || "運用記録あり"
: "本日の運用記録なし";
// 列車運用hub: 編成名リスト
const formationNames =
unyohubEntries
.slice(0, 4)
.map((e) => e.formations)
.join("・") + (unyoCount > 4 ? `${unyoCount - 4}` : "");
return (
<ActionSheet
gestureEnabled
CustomHeaderComponent={<></>}
isModal={Platform.OS === "ios"}
>
{/* ヘッダー */}
<View style={styles.header}>
<View style={styles.handleBar} />
<View style={styles.headerRow}>
<Text style={styles.headerTitle}></Text>
<Text style={styles.headerSub}>{trainNum}</Text>
</View>
</View>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
scrollEnabled={false}
>
{/* ─── jr-shikoku-data-system (列車情報 + 運用情報) ─── */}
<CombinedCard
rows={[
{
icon: "database-search",
title: "列車情報",
sub: hasTrainInfo
? "臨時情報あり / 編成データ・独自データを確認・編集"
: "編成データ・コミュニティ独自データを確認・編集",
badge: hasTrainInfo ? "!" : null,
},
{
icon: "calendar-clock",
title: "運用情報",
sub: unitIdsSub,
badge: opCount > 0 ? opCount : null,
},
]}
color="#0099CC"
label="jr-shikoku-data-system"
onPress={() =>
openWebView(
`https://jr-shikoku-data-system.pages.dev/trainData/${trainNum}?userID=${expoPushToken}&from=eachTrainInfo`,
false
)
}
/>
{/* ─── 列車運用hub ─────────────────────────── */}
<SourceCard
icon="train-car"
color="#FF9800"
title="列車運用hub"
label="外部コミュニティデータ"
sub={unyoCount > 0 ? formationNames : "運用データなし / 新規投稿もここから"}
badge={unyoCount > 0 ? unyoCount : null}
badgeColor="#FF9800"
onPress={() =>
openWebView(
`https://jr-shikoku-data-system.pages.dev/unyohub-connection-train-data/${trainNum}`,
true
)
}
/>
</ScrollView>
<View style={styles.footer} />
</ActionSheet>
);
};
/* ------------------------------------------------------------------ */
/* CombinedCard: 複数行を 1 枠にまとめるカード */
/* ------------------------------------------------------------------ */
type CombinedRow = {
icon: string;
title: string;
sub: string;
badge: number | string | null;
};
const CombinedCard: FC<{
rows: CombinedRow[];
color: string;
label: string;
onPress: () => void;
}> = ({ rows, color, label, onPress }) => (
<TouchableOpacity style={[styles.card, styles.combinedCard]} activeOpacity={0.7} onPress={onPress}>
{/* 共通ヘッダー */}
<View style={styles.combinedHeader}>
<View style={[styles.colorBar, { backgroundColor: color }]} />
<Text style={[styles.labelText, styles.combinedLabel]}>{label}</Text>
</View>
{/* 各行 */}
{rows.map((row, i) => (
<React.Fragment key={row.title}>
{i > 0 && <View style={[styles.divider, { marginLeft: 14 }]} />}
<View style={styles.combinedRow}>
{/* 左カラーバー分のスペーサー */}
<View style={{ width: 4 }} />
<View style={[styles.iconWrap, { backgroundColor: color + "18" }]}>
<MaterialCommunityIcons name={row.icon as any} size={22} color={color} />
</View>
<View style={styles.textWrap}>
<View style={styles.titleRow}>
<Text style={styles.cardTitle}>{row.title}</Text>
{row.badge !== null && (
<View style={[styles.badge, { backgroundColor: color }]}>
<Text style={styles.badgeText}>{row.badge}</Text>
</View>
)}
</View>
<Text style={styles.subText} numberOfLines={1}>{row.sub}</Text>
</View>
{i === rows.length - 1 && (
<MaterialCommunityIcons name="chevron-right" size={20} color="#ccc" style={{ marginRight: 10 }} />
)}
</View>
</React.Fragment>
))}
</TouchableOpacity>
);
/* ------------------------------------------------------------------ */
/* SourceCard */
/* ------------------------------------------------------------------ */
type SourceCardProps = {
icon: string;
color: string;
title: string;
label: string;
sub: string;
badge: number | string | null;
badgeColor: string;
onPress?: () => void;
};
const SourceCard: FC<SourceCardProps> = ({
icon,
color,
title,
label,
sub,
badge,
badgeColor,
onPress,
}) => (
<TouchableOpacity
style={styles.card}
activeOpacity={0.7}
onPress={onPress}
>
{/* 左カラーバー */}
<View style={[styles.colorBar, { backgroundColor: color }]} />
{/* アイコン */}
<View style={[styles.iconWrap, { backgroundColor: color + "18" }]}>
<MaterialCommunityIcons
name={icon as any}
size={24}
color={color}
/>
</View>
{/* テキスト */}
<View style={styles.textWrap}>
<View style={styles.titleRow}>
<Text style={styles.cardTitle}>{title}</Text>
<Text style={styles.labelText}>{label}</Text>
</View>
<Text style={styles.subText} numberOfLines={1}>{sub}</Text>
</View>
{/* バッジ + 矢印 */}
<View style={styles.rightArea}>
{badge !== null ? (
<View style={[styles.badge, { backgroundColor: badgeColor }]}>
<Text style={styles.badgeText}>{badge}</Text>
</View>
) : null}
<MaterialCommunityIcons name="chevron-right" size={20} color="#ccc" />
</View>
</TouchableOpacity>
);
/* ------------------------------------------------------------------ */
/* スタイル */
/* ------------------------------------------------------------------ */
const styles = StyleSheet.create({
header: {
paddingTop: 8,
paddingBottom: 12,
paddingHorizontal: 16,
},
handleBar: {
width: 40,
height: 5,
borderRadius: 3,
backgroundColor: "#ddd",
alignSelf: "center",
marginBottom: 12,
},
headerRow: {
flexDirection: "row",
alignItems: "baseline",
gap: 8,
},
headerTitle: {
fontSize: 17,
fontWeight: "bold",
color: "#111",
},
headerSub: {
fontSize: 14,
color: "#888",
},
scroll: {},
scrollContent: {
paddingHorizontal: 14,
gap: 10,
paddingBottom: 4,
},
card: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#fff",
borderRadius: 12,
borderWidth: 1,
borderColor: "#ebebeb",
overflow: "hidden",
minHeight: 70,
},
combinedCard: {
flexDirection: "column",
alignItems: "stretch",
},
combinedHeader: {
flexDirection: "row",
alignItems: "center",
paddingTop: 8,
paddingBottom: 2,
gap: 8,
},
combinedLabel: {
color: "#888",
marginLeft: 4,
},
combinedRow: {
flexDirection: "row",
alignItems: "center",
minHeight: 58,
},
divider: {
height: StyleSheet.hairlineWidth,
backgroundColor: "#ebebeb",
},
colorBar: {
width: 4,
alignSelf: "stretch",
},
iconWrap: {
width: 48,
height: 48,
borderRadius: 10,
alignItems: "center",
justifyContent: "center",
marginHorizontal: 10,
flexShrink: 0,
},
textWrap: {
flex: 1,
paddingVertical: 10,
gap: 3,
},
titleRow: {
flexDirection: "row",
alignItems: "center",
gap: 6,
},
cardTitle: {
fontSize: 15,
fontWeight: "bold",
color: "#111",
},
cardTitleDisabled: {
color: "#aaa",
},
labelText: {
fontSize: 10,
color: "#aaa",
fontWeight: "500",
},
subText: {
fontSize: 12,
color: "#555",
},
subTextDisabled: {
color: "#bbb",
},
rightArea: {
flexDirection: "row",
alignItems: "center",
paddingRight: 10,
gap: 4,
},
badge: {
minWidth: 20,
height: 20,
borderRadius: 10,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 5,
},
badgeText: {
color: "#fff",
fontSize: 11,
fontWeight: "bold",
},
footer: {
height: 20,
},
});

View File

@@ -6,7 +6,6 @@ import { TrainMenuLineSelector } from "./TrainMenuLineSelector";
import { TrainIconUpdate } from "./TrainIconUpdate";
import { SpecialTrainInfo } from "./SpecialTrainInfo";
import { Social } from "./SocialMenu";
import { TrainDataSources } from "./TrainDataSources";
registerSheet("EachTrainInfo", EachTrainInfo);
registerSheet("JRSTraInfo", JRSTraInfo);
@@ -15,6 +14,5 @@ registerSheet("TrainMenuLineSelector", TrainMenuLineSelector);
registerSheet("TrainIconUpdate", TrainIconUpdate);
registerSheet("SpecialTrainInfo", SpecialTrainInfo);
registerSheet("Social", Social);
registerSheet("TrainDataSources", TrainDataSources);
export {};

View File

@@ -1,150 +0,0 @@
import React, { useState, useEffect } from "react";
import { View, Text, ScrollView, StyleSheet } from "react-native";
import { Switch } from "react-native-elements";
import { useNavigation } from "@react-navigation/native";
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
import { AS } from "../../storageControl";
import { STORAGE_KEYS } from "@/constants";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
export const DataSourceSettings = () => {
const navigation = useNavigation();
const { updatePermission, dataSourcePermission } = useTrainMenu();
const canAccess = updatePermission || Object.values(dataSourcePermission).some(Boolean);
const [useUnyohub, setUseUnyohub] = useState(false);
useEffect(() => {
AS.getItem(STORAGE_KEYS.USE_UNYOHUB).then((value) => {
setUseUnyohub(value === true || value === "true");
});
}, []);
const handleToggleUnyohub = (value: boolean) => {
setUseUnyohub(value);
AS.setItem(STORAGE_KEYS.USE_UNYOHUB, value.toString());
};
return (
<View style={styles.container}>
<SheetHeaderItem
title="情報ソース設定"
LeftItem={{
title: "戻る",
onPress: () => navigation.goBack(),
}}
/>
{!canAccess ? (
<View style={styles.noPermissionContainer}>
<Text style={styles.noPermissionText}></Text>
<Text style={styles.noPermissionSubText}>hubまたはアプリ管理者の権限が必要です</Text>
</View>
) : (
<ScrollView style={styles.content}>
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.settingItem}>
<View style={styles.settingTextContainer}>
<Text style={styles.settingTitle}>hub</Text>
<Text style={styles.settingDescription}>
{"\n"}
</Text>
</View>
<Switch
value={useUnyohub}
onValueChange={handleToggleUnyohub}
color="#0099CC"
/>
</View>
</View>
<View style={styles.infoSection}>
<Text style={styles.infoText}>
{"\n\n"}
</Text>
</View>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
noPermissionContainer: {
flex: 1,
backgroundColor: "#f8f8fc",
alignItems: "center",
justifyContent: "center",
padding: 30,
gap: 10,
},
noPermissionText: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
textAlign: "center",
},
noPermissionSubText: {
fontSize: 13,
color: "#666",
textAlign: "center",
},
container: {
flex: 1,
backgroundColor: "#0099CC",
},
content: {
flex: 1,
backgroundColor: "#f8f8fc",
},
section: {
backgroundColor: "white",
marginTop: 20,
marginHorizontal: 10,
borderRadius: 10,
padding: 15,
},
sectionTitle: {
fontSize: 18,
fontWeight: "bold",
color: "#333",
marginBottom: 15,
},
settingItem: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
},
settingTextContainer: {
flex: 1,
marginRight: 10,
},
settingTitle: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
marginBottom: 5,
},
settingDescription: {
fontSize: 13,
color: "#666",
lineHeight: 18,
},
infoSection: {
backgroundColor: "#fff3cd",
marginTop: 20,
marginHorizontal: 10,
borderRadius: 10,
padding: 15,
marginBottom: 30,
},
infoText: {
fontSize: 13,
color: "#856404",
lineHeight: 18,
},
});

View File

@@ -16,7 +16,6 @@ import TouchableScale from "react-native-touchable-scale";
import { SwitchArea } from "../atom/SwitchArea";
import { useNotification } from "../../stateBox/useNotifications";
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
import { useTrainMenu } from "../../stateBox/useTrainMenu";
const versionCode = "6.2.1"; // Update this version code as needed
@@ -28,11 +27,7 @@ export const SettingTopPage = ({
}) => {
const { width } = useWindowDimensions();
const { expoPushToken } = useNotification();
const { updatePermission, dataSourcePermission } = useTrainMenu();
const navigation = useNavigation();
// admin またはいずれかのソース権限を持つ場合のみ表示
const canAccessDataSourceSettings =
updatePermission || Object.values(dataSourcePermission).some(Boolean);
return (
<View style={{ height: "100%", backgroundColor: "#0099CC" }}>
<SheetHeaderItem title="アプリの設定画面" LeftItem={{
@@ -113,14 +108,6 @@ export const SettingTopPage = ({
navigation.navigate("setting", { screen: "LayoutSettings" })
}
/>
{canAccessDataSourceSettings && (
<SettingList
string="情報ソース設定"
onPress={() =>
navigation.navigate("setting", { screen: "DataSourceSettings" })
}
/>
)}
{Platform.OS === "android" ? (
<SettingList
string="ウィジェット設定"

View File

@@ -24,7 +24,6 @@ import { FavoriteSettings } from "./FavoriteSettings";
import { WidgetSettings } from "./WidgetSettings";
import { NotificationSettings } from "./NotificationSettings";
import { LauncherIconSettings } from "./LauncherIconSettings";
import { DataSourceSettings } from "./DataSourceSettings";
const Stack = createStackNavigator();
export default function Setting(props) {
@@ -168,17 +167,6 @@ export default function Setting(props) {
}}
component={FavoriteSettings}
/>
<Stack.Screen
name="DataSourceSettings"
options={{
gestureEnabled: true,
...TransitionPresets.SlideFromRightIOS,
cardOverlayEnabled: true,
headerTransparent: true,
headerShown: false,
}}
component={DataSourceSettings}
/>
</Stack.Navigator>
);
}

View File

@@ -55,8 +55,8 @@ export const ExGridSimpleViewItem: FC<{
type: trainData?.type,
whiteMode: true,
});
// 行き先(駅名)の取得
const [destinationName] = useMemo(() => {
// 列車名、種別、フォントの取得
const [trainName] = useMemo(() => {
// to_dataが設定されていればそれを優先
if (trainData?.to_data) {
return [trainData.to_data];
@@ -75,9 +75,6 @@ export const ExGridSimpleViewItem: FC<{
return [migrateTrainName(trainName)];
}
}, [d.array, trainData]);
// 列車名の取得(上部表示用)
const trainName = trainData?.train_name || "";
const timeArray = d.time.split(":").map((s) => parseInt(s));
const formattedTime = dayjs()
.set("hour", timeArray[0])
@@ -149,7 +146,7 @@ export const ExGridSimpleViewItem: FC<{
// to_dataがある場合は、to_dataから駅名を抽出して色を判定
const stationNameForColor = trainData?.to_data
? trainData.to_data.replace(/行き$/, "") // 「行き」を除去
: destinationName;
: trainName;
const Stations = stationList
.map((a) => a.filter((d) => d.StationName == stationNameForColor))
@@ -164,7 +161,7 @@ export const ExGridSimpleViewItem: FC<{
);
setStationColor(stationLineColor || ["gray"]);
}
}, [stationList, destinationName, trainData]);
}, [stationList, trainName, trainData]);
// if(typeString == "回送"){
// return<></>;
// }
@@ -240,7 +237,7 @@ export const ExGridSimpleViewItem: FC<{
textDecorationLine: isCancelled ? "line-through" : "none",
}}
>
{destinationName}
{trainName}
</Text>
</View>
<View style={{ flex: 1 }} />

View File

@@ -13,7 +13,6 @@ import { TrainName } from "@/components/発車時刻表/LED_inside_Component/Tra
import { TrainPosition } from "@/components/発車時刻表/LED_inside_Component/TrainPosition";
import { StationPosPushDialog } from "@/components/発車時刻表/LED_inside_Component/TrainPositionDataPush";
import { StationPosDeleteDialog } from "@/components/発車時刻表/LED_inside_Component/TrainPositionDataDelete";
import { ScrollingDescription } from "@/components/発車時刻表/LED_inside_Component/ScrollingDescription";
import { useStationList } from "@/stateBox/useStationList";
import useInterval from "@/lib/useInterval";
import dayjs from "dayjs";
@@ -198,19 +197,17 @@ export const EachData: FC<Props> = (props) => {
trainID={d.train}
type={train.type}
isThrew={d.isThrough}
se={d.se}
/>
<LastStation
lastStation={d.lastStation}
ToData={train.to_data}
Station_JP={station.Station_JP}
se={d.se}
/>
<PlatformNumber platform={d.platformNum} se={d.se} />
<PlatformNumber platform={d.platformNum} />
{timeDisplay ? (
<DependTime time={d.time} isDelay={isDelay} se={d.se} />
<DependTime time={d.time} isDelay={isDelay} />
) : (
<StatusAndDelay trainDelayStatus={trainDelayStatus} se={d.se} />
<StatusAndDelay trainDelayStatus={trainDelayStatus} />
)}
</TouchableOpacity>
{!!isDepartureNow && (
@@ -246,20 +243,7 @@ export const EachData: FC<Props> = (props) => {
/>
)}
{trainDescriptionSwitch && !!train.train_info && (
<TouchableOpacity
style={{
alignContent: "center",
alignItems: "center",
width: "94%",
marginVertical: 5,
marginHorizontal: "3%",
backgroundColor: "#000",
overflow: "hidden",
}}
key={d.train + "-description"}
>
<ScrollingDescription description={train.train_info} />
</TouchableOpacity>
<Description info={train.train_info} key={d.train + "-description"} />
)}
{trainDescriptionSwitch && !!train.uwasa && (
<Description info={train.uwasa} key={d.train + "-uwasa"} />

View File

@@ -7,17 +7,13 @@ const descriptionStyle: TextStyle = {
type Props = {
time: string;
isDelay?: boolean;
se?: string;
};
export const DependTime: FC<Props> = ({ time, isDelay, se }) => {
const isCanceled = se?.includes("休");
return (
<View style={{ flex: 4 }}>
<Text
style={{ ...descriptionStyle, color: isCanceled ? "#999" : isDelay ? "#ffd16fff" : "white", textDecorationLine: isCanceled ? "line-through" : "none" }}
>
{time}
</Text>
</View>
);
};
export const DependTime: FC<Props> = ({ time, isDelay }) => (
<View style={{ flex: 4 }}>
<Text
style={{ ...descriptionStyle, color: isDelay ? "#ffd16fff" : "white" }}
>
{time}
</Text>
</View>
);

View File

@@ -5,33 +5,17 @@ type Props = {
lastStation: string;
ToData: string;
Station_JP: string;
se?: string;
};
export const LastStation: FC<Props> = ({ lastStation, ToData, Station_JP, se }) => {
export const LastStation: FC<Props> = ({ lastStation, ToData, Station_JP }) => {
const isEdit = !ToData ? false : ToData !== lastStation;
const string = isEdit ? ToData : lastStation;
const isCanceled = se?.includes("休");
return (
<View style={{ flex: 4, flexDirection: "row" }}>
{isCanceled && (
<Text
style={{
fontSize: parseInt("12%"),
color: "#ff6b6b",
fontWeight: "bold",
marginRight: 4,
}}
>
</Text>
)}
<Text
style={{
fontSize: lastStation?.length > 4 ? parseInt("12%") : parseInt("16%"),
color: isCanceled ? "#999" : isEdit ? "#ffd16fff" : "white",
color: isEdit ? "#ffd16fff" : "white",
fontWeight: "bold",
textDecorationLine: isCanceled ? "line-through" : "none",
}}
>
{string === Station_JP ? "当駅止" : string}

View File

@@ -6,13 +6,11 @@ const descriptionStyle: TextStyle = {
};
type Props = {
platform: string;
se?: string;
};
export const PlatformNumber: FC<Props> = ({ platform, se }) => {
const isCanceled = se?.includes("休");
export const PlatformNumber: FC<Props> = ({ platform }) => {
return (
<View style={{ flex: 2 }}>
<Text style={{ ...descriptionStyle, color: isCanceled ? "#999" : "white", paddingLeft: 1, textDecorationLine: isCanceled ? "line-through" : "none" }}>
<Text style={{ ...descriptionStyle, color: "white", paddingLeft: 1 }}>
{platform}
</Text>
</View>

View File

@@ -1,127 +0,0 @@
import React, { FC, useEffect, useRef, useState } from "react";
import { Animated, Text, View, LayoutChangeEvent } from "react-native";
type Props = {
description: string;
};
export const ScrollingDescription: FC<Props> = ({ description }) => {
const scrollX = useRef(new Animated.Value(0)).current;
const [textWidth, setTextWidth] = useState(0);
const [containerWidth, setContainerWidth] = useState(0);
// 改行を削除して1行にする
const singleLineDescription = description?.replace(/\n/g, " ") || "";
useEffect(() => {
if (!singleLineDescription || textWidth === 0 || containerWidth === 0) {
return;
}
// テキストが画面幅より短い場合はスクロールしない
if (textWidth <= containerWidth) {
return;
}
// 初期位置を設定(画面の右端から開始)
scrollX.setValue(containerWidth);
const distance = textWidth + containerWidth;
const duration = distance * 10; // スクロール速度
const animation = Animated.loop(
Animated.sequence([
Animated.delay(500), // 最初に0.5秒待つ
Animated.timing(scrollX, {
toValue: -textWidth - 20,
duration: duration,
useNativeDriver: true,
}),
Animated.delay(500),
// 瞬時に右端に戻る
Animated.timing(scrollX, {
toValue: containerWidth,
duration: 0,
useNativeDriver: true,
}),
])
);
animation.start();
return () => {
animation.stop();
scrollX.setValue(containerWidth);
};
}, [singleLineDescription, textWidth, containerWidth]);
const handleTextLayout = (event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout;
if (width > 0) {
setTextWidth(width);
}
};
const handleContainerLayout = (event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout;
if (width > 0) {
setContainerWidth(width);
}
};
if (!singleLineDescription) {
return null;
}
return (
<View
style={{
width: "100%",
height: 20,
overflow: "hidden",
backgroundColor: "#000",
}}
onLayout={handleContainerLayout}
>
{/* 測定用の透明なテキスト(画面外、幅制限なし) */}
<View
style={{
position: "absolute",
top: -1000,
left: 0,
width: 9999, // 十分な幅を確保してテキストが折り返されないようにする
opacity: 0,
}}
>
<Text
style={{
fontSize: 16,
fontWeight: "bold",
}}
onLayout={handleTextLayout}
>
{singleLineDescription}
</Text>
</View>
{/* 実際に表示されるスクロールテキスト(幅制限なし) */}
<Animated.View
style={{
position: "absolute",
transform: [{ translateX: scrollX }],
width: 9999, // テキストが折り返されないように十分な幅を確保
}}
>
<Text
style={{
fontSize: 16,
fontWeight: "bold",
color: "#d3a203",
}}
>
{singleLineDescription}
</Text>
</Animated.View>
</View>
);
};

View File

@@ -6,13 +6,11 @@ const descriptionStyle: TextStyle = {
};
type Props = {
trainDelayStatus: string;
se?: string;
};
export const StatusAndDelay: FC<Props> = ({ trainDelayStatus, se }) => {
const isCanceled = se?.includes("休");
export const StatusAndDelay: FC<Props> = ({ trainDelayStatus }) => {
return (
<View style={{ flex: 4 }}>
<Text style={{ ...descriptionStyle, color: isCanceled ? "#999" : "#ffd16fff", paddingLeft: 1, textDecorationLine: isCanceled ? "line-through" : "none" }}>
<Text style={{ ...descriptionStyle, color: "#ffd16fff", paddingLeft: 1 }}>
{trainDelayStatus}
</Text>
</View>

View File

@@ -9,10 +9,9 @@ type Props = {
trainID: string;
type: trainTypeID;
isThrew: boolean;
se?: string;
};
export const TrainName: FC<Props> = (props) => {
const { trainName, trainNumDistance, trainIDSwitch, trainID, type, isThrew, se } =
const { trainName, trainNumDistance, trainIDSwitch, trainID, type, isThrew } =
props;
const { name, color } = getTrainType({ type });
const TrainNumber =
@@ -24,15 +23,13 @@ export const TrainName: FC<Props> = (props) => {
parseInt(trainNumDistance)
}`
: "";
const isCanceled = se?.includes("休");
return (
<View style={{ flex: 9, flexDirection: "row", alignItems: "center" }}>
<Text
style={{
fontSize: trainName.length > 6 ? parseInt("11%") : parseInt("15%"),
color: isCanceled ? "#999" : color,
color: color,
fontWeight: "bold",
textDecorationLine: isCanceled ? "line-through" : "none",
}}
>
{trainIDSwitch

View File

@@ -31,9 +31,6 @@ export const API_ENDPOINTS = {
/** 位置情報問題データ */
POSITION_PROBLEMS: 'https://n8n.haruk.in/webhook/jrshikoku-position-problems',
/** 列車運用hub運用データ */
UNYOHUB_DATA: 'https://jr-shikoku-api-data-storage.haruk.in/thirdparty/unyohub-unyo.json',
} as const;
/**

View File

@@ -80,13 +80,6 @@ export const STORAGE_KEYS = {
/** 奇妙な列車通知 */
STRANGE_TRAIN: 'strangeTrain',
// 情報ソース設定系
/** 列車運用hub使用設定 */
USE_UNYOHUB: 'useUnyohub',
/** 列車運用hubデータ */
UNYOHUB_DATA: 'unyohubData',
} as const;
/**

View File

@@ -70,7 +70,6 @@ export type CustomTrainData = {
lastStation: string;
isThrough: boolean;
platformNum: string | null;
se?: string;
};
export type StationProps = {
@@ -94,7 +93,6 @@ export type OperationLogs = {
train_ids?: string[];
unit_ids?: string[];
vehicle_img: string;
vehicle_img_right: string;
vehicle_info_url: string;
related_train_ids?: string[];
state: number | null;

View File

@@ -74,7 +74,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
isThrough: false,
train: trainNum,
platformNum: null,
se: undefined,
};
stationDiagram[trainNum].split("#").forEach((data) => {
const [stationName, type, time, platformNum] = data.split(",");
@@ -84,7 +83,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
}
if (stationName === station.Station_JP) {
trainData.platformNum = platformNum;
trainData.se = type;
if (type.match("発")) {
trainData.time = time;
} else if (type.match("通")) {
@@ -101,7 +99,6 @@ export const getTime: getTimeProps = (stationDiagram, station) => {
lastStation: trainData.lastStation,
isThrough: trainData.isThrough,
platformNum: trainData.platformNum,
se: trainData.se,
};
})
.filter((d) => d.time);

View File

@@ -1,5 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { AppState, AppStateStatus, Platform } from "react-native";
type Control = {
start: () => void;
@@ -13,15 +12,10 @@ type Fn = () => void;
export const useInterval = (fn: Fn, interval: number, autostart = true) => {
const onUpdateRef = useRef<Fn>();
const [state, setState] = useState("RUNNING");
// ユーザー操作によるSTOPAppStateによる一時停止と区別する
const userStoppedRef = useRef(!autostart);
const start = () => {
userStoppedRef.current = false;
setState("RUNNING");
};
const stop = () => {
userStoppedRef.current = true;
setState("STOPPED");
};
useEffect(() => {
@@ -29,48 +23,22 @@ export const useInterval = (fn: Fn, interval: number, autostart = true) => {
}, [fn]);
useEffect(() => {
if (autostart) {
userStoppedRef.current = false;
setState("RUNNING");
} else {
userStoppedRef.current = true;
}else{
setState("STOPPED");
}
}, [autostart]);
// バックグラウンド移行時に停止、フォアグラウンド復帰時に即時実行して再開
useEffect(() => {
if (Platform.OS === "web") return;
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === "active") {
if (!userStoppedRef.current) {
// 復帰直後に即時フェッチして最新データを取得
onUpdateRef.current?.();
setState("RUNNING");
}
} else if (nextAppState === "background" || nextAppState === "inactive") {
if (!userStoppedRef.current) {
// バックグラウンド中はインターバルを停止してムダなfetchエラーを防ぐ
setState("STOPPED");
}
}
};
const subscription = AppState.addEventListener("change", handleAppStateChange);
return () => {
subscription.remove();
};
}, []);
useEffect(() => {
let timerId: ReturnType<typeof setInterval> | undefined;
let timerId;
if (state === "RUNNING") {
timerId = setInterval(() => {
onUpdateRef.current?.();
}, interval);
} else {
if (timerId) clearInterval(timerId);
timerId && clearInterval(timerId);
}
return () => {
if (timerId) clearInterval(timerId);
timerId && clearInterval(timerId);
};
}, [interval, state]);
return [state, { start, stop }];

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +0,0 @@
/**
* 駅情報データ (みどりの窓口・ICカード対応状況)
* Feature フィールドは WebView 内で JSON.parse() して使用する。
*/
export interface StationDataItem {
StationName: string;
StationNumber: string;
/** JSON文字列: { Midori: { style: "normal"|"plus"|"none" }, IC: boolean } */
Feature: string;
}
export const STATION_DATA: StationDataItem[] = [
// ── 予讃線 ─────────────────────────────
{ StationName: "高松", StationNumber: "Y00", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "香西", StationNumber: "Y01", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "鬼無", StationNumber: "Y02", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "端岡", StationNumber: "Y03", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "国分", StationNumber: "Y04", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "讃岐府中", StationNumber: "Y05", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "鴨川", StationNumber: "Y06", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "八十場", StationNumber: "Y07", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "坂出", StationNumber: "Y08", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "宇多津", StationNumber: "Y09", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "丸亀", StationNumber: "Y10", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "讃岐塩屋", StationNumber: "Y11", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "多度津", StationNumber: "Y12", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "詫間", StationNumber: "Y14", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
{ StationName: "観音寺", StationNumber: "Y19", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "川之江", StationNumber: "Y22", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "伊予三島", StationNumber: "Y23", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "新居浜", StationNumber: "Y29", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "伊予西条", StationNumber: "Y31", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "壬生川", StationNumber: "Y36", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "今治", StationNumber: "Y40", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "伊予北条", StationNumber: "Y48", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "松山", StationNumber: "Y55", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
// ── 内子線・海線 ─────────────────────────
{ StationName: "内子", StationNumber: "U10", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "伊予大洲", StationNumber: "U14", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "伊予大洲", StationNumber: "S18", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "八幡浜", StationNumber: "U18", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "宇和島", StationNumber: "U28", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
// ── 土讃線 ─────────────────────────────
{ StationName: "多度津", StationNumber: "D12", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "善通寺", StationNumber: "D14", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
{ StationName: "琴平", StationNumber: "D15", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
{ StationName: "阿波池田", StationNumber: "D22", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "土佐山田", StationNumber: "D37", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "後免", StationNumber: "D40", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "高知", StationNumber: "D45", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
// ── 予土線 ─────────────────────────────
{ StationName: "高知", StationNumber: "K00", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "朝倉", StationNumber: "K05", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "須崎", StationNumber: "K19", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "窪川", StationNumber: "K26", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
// ── 高徳線 ─────────────────────────────
{ StationName: "高松", StationNumber: "T28", Feature: '{"Midori":{"style":"normal"},"IC":true}' },
{ StationName: "栗林公園北口", StationNumber: "T26", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "栗林", StationNumber: "T25", Feature: '{"Midori":{"style":"plus"},"IC":true}' },
{ StationName: "屋島", StationNumber: "T24", Feature: '{"Midori":{"style":"none"},"IC":true}' },
{ StationName: "志度", StationNumber: "T19", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "三本松", StationNumber: "T12", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "徳島", StationNumber: "T00", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
// ── 鳴門線 ─────────────────────────────
{ StationName: "鳴門", StationNumber: "N10", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
// ── 牟岐線 ─────────────────────────────
{ StationName: "阿南", StationNumber: "", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
{ StationName: "牟岐", StationNumber: "", Feature: '{"Midori":{"style":"normal"},"IC":false}' },
// ── 徳島線 ─────────────────────────────
{ StationName: "鴨島", StationNumber: "B09", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
{ StationName: "穴吹", StationNumber: "B16", Feature: '{"Midori":{"style":"plus"},"IC":false}' },
];

View File

@@ -1,455 +0,0 @@
/**
* 列番号 → 列車画像URL のマッピングデータ
*
* "__anpanman__" は動的URLのセンチネル値。
* WebView inject 側で アンパンマン列車判定 URL に変換される。
* (https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=<列番>)
*/
const AP = "__anpanman__";
/** 完全一致マッピング (列番 → URL | "__anpanman__") */
export const TRAIN_ICON_MAP: Record<string, string> = {
// ── しおかぜ ───────────────────────────────────────
// 8000 ノーマル
"2M": "https://storage.haruk.in/s8000nr.png",
"4M": "https://storage.haruk.in/s8000nr.png",
"6M": "https://storage.haruk.in/s8000nr.png",
"14M": "https://storage.haruk.in/s8000nr.png",
"16M": "https://storage.haruk.in/s8000nr.png",
"18M": "https://storage.haruk.in/s8000nr.png",
"26M": "https://storage.haruk.in/s8000nr.png",
"28M": "https://storage.haruk.in/s8000nr.png",
"30M": "https://storage.haruk.in/s8000nr.png",
"1M": "https://storage.haruk.in/s8000nr.png",
"3M": "https://storage.haruk.in/s8000nr.png",
"5M": "https://storage.haruk.in/s8000nr.png",
"13M": "https://storage.haruk.in/s8000nr.png",
"15M": "https://storage.haruk.in/s8000nr.png",
"17M": "https://storage.haruk.in/s8000nr.png",
"25M": "https://storage.haruk.in/s8000nr.png",
"27M": "https://storage.haruk.in/s8000nr.png",
"29M": "https://storage.haruk.in/s8000nr.png",
// 8000 アンパン
"10M": AP,
"22M": AP,
"9M": AP,
"21M": AP,
// 8600
"8M": "https://storage.haruk.in/s8600.png",
"12M": "https://storage.haruk.in/s8600.png",
"20M": "https://storage.haruk.in/s8600.png",
"24M": "https://storage.haruk.in/s8600.png",
"7M": "https://storage.haruk.in/s8600.png",
"11M": "https://storage.haruk.in/s8600.png",
"19M": "https://storage.haruk.in/s8600.png",
"23M": "https://storage.haruk.in/s8600.png",
// ── いしづち ───────────────────────────────────────
// 8000 ノーマル
"1004M": "https://storage.haruk.in/s8000no.png",
"1006M": "https://storage.haruk.in/s8000no.png",
"1014M": "https://storage.haruk.in/s8000no.png",
"1016M": "https://storage.haruk.in/s8000no.png",
"1018M": "https://storage.haruk.in/s8000no.png",
"1026M": "https://storage.haruk.in/s8000no.png",
"1028M": "https://storage.haruk.in/s8000no.png",
"1030M": "https://storage.haruk.in/s8000no.png",
"1001M": "https://storage.haruk.in/s8000no.png",
"1003M": "https://storage.haruk.in/s8000no.png",
"1005M": "https://storage.haruk.in/s8000no.png",
"1013M": "https://storage.haruk.in/s8000no.png",
"1015M": "https://storage.haruk.in/s8000no.png",
"1017M": "https://storage.haruk.in/s8000no.png",
"1025M": "https://storage.haruk.in/s8000no.png",
"1027M": "https://storage.haruk.in/s8000no.png",
"1029M": "https://storage.haruk.in/s8000no.png",
// 8000 アンパン
"1010M": AP,
"1022M": AP,
"1009M": AP,
"1021M": AP,
// 8600
"1008M": "https://storage.haruk.in/s8600_isz.png",
"1012M": "https://storage.haruk.in/s8600_isz.png",
"1020M": "https://storage.haruk.in/s8600_isz.png",
"1024M": "https://storage.haruk.in/s8600_isz.png",
"1007M": "https://storage.haruk.in/s8600_isz.png",
"1011M": "https://storage.haruk.in/s8600_isz.png",
"1019M": "https://storage.haruk.in/s8600_isz.png",
"1023M": "https://storage.haruk.in/s8600_isz.png",
// MEXP
"1092M": "https://storage.haruk.in/s8000nr.png",
"1091M": "https://storage.haruk.in/s8600_isz.png",
// 三桁いしづち アンパン
"1041M": AP,
"1044M": AP,
// 三桁いしづち 8600
"1043M": "https://storage.haruk.in/s8600_isz.png",
"1042M": "https://storage.haruk.in/s8600_isz.png",
"1046M": "https://storage.haruk.in/s8600_isz.png",
// ── 南風 ───────────────────────────────────────────
// 2700 ノーマル
"34D": "https://storage.haruk.in/s2700.png",
"38D": "https://storage.haruk.in/s2700.png",
"40D": "https://storage.haruk.in/s2700.png",
"42D": "https://storage.haruk.in/s2700.png",
"46D": "https://storage.haruk.in/s2700.png",
"50D": "https://storage.haruk.in/s2700.png",
"52D": "https://storage.haruk.in/s2700.png",
"54D": "https://storage.haruk.in/s2700.png",
"58D": "https://storage.haruk.in/s2700.png",
"31D": "https://storage.haruk.in/s2700.png",
"35D": "https://storage.haruk.in/s2700.png",
"39D": "https://storage.haruk.in/s2700.png",
"41D": "https://storage.haruk.in/s2700.png",
"43D": "https://storage.haruk.in/s2700.png",
"47D": "https://storage.haruk.in/s2700.png",
"51D": "https://storage.haruk.in/s2700.png",
"53D": "https://storage.haruk.in/s2700.png",
"55D": "https://storage.haruk.in/s2700.png",
// 2700 アンパン
"32D": AP,
"36D": AP,
"44D": AP,
"48D": AP,
"56D": AP,
"33D": AP,
"37D": AP,
"45D": AP,
"49D": AP,
"57D": AP,
// ── うずしお ───────────────────────────────────────
// 2700
"3004D": "https://storage.haruk.in/s2700_uzu.png",
"3006D": "https://storage.haruk.in/s2700_uzu.png",
"3010D": "https://storage.haruk.in/s2700_uzu.png",
"3014D": "https://storage.haruk.in/s2700_uzu.png",
"3016D": "https://storage.haruk.in/s2700_uzu.png",
"3022D": "https://storage.haruk.in/s2700_uzu.png",
"3028D": "https://storage.haruk.in/s2700_uzu.png",
"3003D": "https://storage.haruk.in/s2700_uzu.png",
"3007D": "https://storage.haruk.in/s2700_uzu.png",
"3013D": "https://storage.haruk.in/s2700_uzu.png",
"3019D": "https://storage.haruk.in/s2700_uzu.png",
"3025D": "https://storage.haruk.in/s2700_uzu.png",
"3031D": "https://storage.haruk.in/s2700_uzu.png",
"3008D": "https://storage.haruk.in/s2700_uzu.png",
"3020D": "https://storage.haruk.in/s2700_uzu.png",
"3026D": "https://storage.haruk.in/s2700_uzu.png",
"3001D": "https://storage.haruk.in/s2700_uzu.png",
"3005D": "https://storage.haruk.in/s2700_uzu.png",
"3011D": "https://storage.haruk.in/s2700_uzu.png",
"3017D": "https://storage.haruk.in/s2700_uzu.png",
"3023D": "https://storage.haruk.in/s2700_uzu.png",
"3029D": "https://storage.haruk.in/s2700_uzu.png",
// 2600
"3002D": AP,
"3012D": AP,
"3018D": AP,
"3024D": AP,
"3030D": AP,
"3009D": AP,
"3015D": AP,
"3021D": AP,
"3027D": AP,
"3033D": AP,
// ── マリンライナー ─────────────────────────────────
"3104M": "https://storage.haruk.in/s5001.png",
"3106M": "https://storage.haruk.in/s5001.png",
"3108M": "https://storage.haruk.in/s5001.png",
"3110M": "https://storage.haruk.in/s5001.png",
"3112M": "https://storage.haruk.in/s5001.png",
"3114M": "https://storage.haruk.in/s5001.png",
"3116M": "https://storage.haruk.in/s5001.png",
"3118M": "https://storage.haruk.in/s5001.png",
"3120M": "https://storage.haruk.in/s5001.png",
"3122M": "https://storage.haruk.in/s5001.png",
"3124M": "https://storage.haruk.in/s5001.png",
"3126M": "https://storage.haruk.in/s5001.png",
"3128M": "https://storage.haruk.in/s5001.png",
"3130M": "https://storage.haruk.in/s5001.png",
"3132M": "https://storage.haruk.in/s5001.png",
"3134M": "https://storage.haruk.in/s5001.png",
"3136M": "https://storage.haruk.in/s5001.png",
"3138M": "https://storage.haruk.in/s5001.png",
"3140M": "https://storage.haruk.in/s5001.png",
"3142M": "https://storage.haruk.in/s5001.png",
"3144M": "https://storage.haruk.in/s5001.png",
"3146M": "https://storage.haruk.in/s5001.png",
"3148M": "https://storage.haruk.in/s5001.png",
"3150M": "https://storage.haruk.in/s5001.png",
"3152M": "https://storage.haruk.in/s5001.png",
"3154M": "https://storage.haruk.in/s5001.png",
"3156M": "https://storage.haruk.in/s5001.png",
"3158M": "https://storage.haruk.in/s5001.png",
"3160M": "https://storage.haruk.in/s5001.png",
"3162M": "https://storage.haruk.in/s5001.png",
"3164M": "https://storage.haruk.in/s5001.png",
"3166M": "https://storage.haruk.in/s5001.png",
"3168M": "https://storage.haruk.in/s5001.png",
"3170M": "https://storage.haruk.in/s5001.png",
"3105M": "https://storage.haruk.in/s5001.png",
"3107M": "https://storage.haruk.in/s5001.png",
"3109M": "https://storage.haruk.in/s5001.png",
"3111M": "https://storage.haruk.in/s5001.png",
"3113M": "https://storage.haruk.in/s5001.png",
"3115M": "https://storage.haruk.in/s5001.png",
"3117M": "https://storage.haruk.in/s5001.png",
"3119M": "https://storage.haruk.in/s5001.png",
"3121M": "https://storage.haruk.in/s5001.png",
"3123M": "https://storage.haruk.in/s5001.png",
"3125M": "https://storage.haruk.in/s5001.png",
"3127M": "https://storage.haruk.in/s5001.png",
"3129M": "https://storage.haruk.in/s5001.png",
"3131M": "https://storage.haruk.in/s5001.png",
"3133M": "https://storage.haruk.in/s5001.png",
"3135M": "https://storage.haruk.in/s5001.png",
"3137M": "https://storage.haruk.in/s5001.png",
"3139M": "https://storage.haruk.in/s5001.png",
"3141M": "https://storage.haruk.in/s5001.png",
"3143M": "https://storage.haruk.in/s5001.png",
"3145M": "https://storage.haruk.in/s5001.png",
"3147M": "https://storage.haruk.in/s5001.png",
"3149M": "https://storage.haruk.in/s5001.png",
"3151M": "https://storage.haruk.in/s5001.png",
"3153M": "https://storage.haruk.in/s5001.png",
"3155M": "https://storage.haruk.in/s5001.png",
"3157M": "https://storage.haruk.in/s5001.png",
"3159M": "https://storage.haruk.in/s5001.png",
"3161M": "https://storage.haruk.in/s5001.png",
"3163M": "https://storage.haruk.in/s5001.png",
"3165M": "https://storage.haruk.in/s5001.png",
"3167M": "https://storage.haruk.in/s5001.png",
"3169M": "https://storage.haruk.in/s5001.png",
"3175M": "https://storage.haruk.in/s5001.png",
// マリンライナー(快速)
"3102M": "https://storage.haruk.in/s5001k.png",
"3101M": "https://storage.haruk.in/s5001k.png",
"3103M": "https://storage.haruk.in/s5001k.png",
"3171M": "https://storage.haruk.in/s5001k.png",
"3173M": "https://storage.haruk.in/s5001k.png",
// ── サンライズ瀬戸 ─────────────────────────────────
"5032M": "https://storage.haruk.in/w285.png",
"5031M": "https://storage.haruk.in/w285.png",
"8041M": "https://storage.haruk.in/w285.png",
"8031M": "https://storage.haruk.in/w285.png",
// ── 宇和海 ─────────────────────────────────────────
// 2000 ノーマル
"1052D": "https://storage.haruk.in/s2000_uwa.png",
"1054D": "https://storage.haruk.in/s2000_uwa.png",
"1056D": "https://storage.haruk.in/s2000_uwa.png",
"1060D": "https://storage.haruk.in/s2000_uwa.png",
"1062D": "https://storage.haruk.in/s2000_uwa.png",
"1064D": "https://storage.haruk.in/s2000_uwa.png",
"1068D": "https://storage.haruk.in/s2000_uwa.png",
"1070D": "https://storage.haruk.in/s2000_uwa.png",
"1072D": "https://storage.haruk.in/s2000_uwa.png",
"1076D": "https://storage.haruk.in/s2000_uwa.png",
"1078D": "https://storage.haruk.in/s2000_uwa.png",
"1080D": "https://storage.haruk.in/s2000_uwa.png",
"1082D": "https://storage.haruk.in/s2000_uwa.png",
"1051D": "https://storage.haruk.in/s2000_uwa.png",
"1055D": "https://storage.haruk.in/s2000_uwa.png",
"1057D": "https://storage.haruk.in/s2000_uwa.png",
"1061D": "https://storage.haruk.in/s2000_uwa.png",
"1063D": "https://storage.haruk.in/s2000_uwa.png",
"1065D": "https://storage.haruk.in/s2000_uwa.png",
"1069D": "https://storage.haruk.in/s2000_uwa.png",
"1071D": "https://storage.haruk.in/s2000_uwa.png",
"1073D": "https://storage.haruk.in/s2000_uwa.png",
"1075D": "https://storage.haruk.in/s2000_uwa.png",
"1077D": "https://storage.haruk.in/s2000_uwa.png",
"1079D": "https://storage.haruk.in/s2000_uwa.png",
"1081D": "https://storage.haruk.in/s2000_uwa.png",
// 2000 アンパン
"1058D": AP,
"1066D": AP,
"1074D": AP,
"1053D": AP,
"1059D": AP,
"1067D": AP,
// ── しまんと ───────────────────────────────────────
"2002D": "https://storage.haruk.in/s2000_smn.png",
"2004D": "https://storage.haruk.in/s2000_smn.png",
"2001D": "https://storage.haruk.in/s2000_smn.png",
"2003D": "https://storage.haruk.in/s2000_smn.png",
// ── あしずり ───────────────────────────────────────
// 2000
"2074D": "https://storage.haruk.in/s2000_smn.png",
"2076D": "https://storage.haruk.in/s2000_smn.png",
"2080D": "https://storage.haruk.in/s2000_smn.png",
"2082D": "https://storage.haruk.in/s2000_smn.png",
"2071D": "https://storage.haruk.in/s2000_smn.png",
"2073D": "https://storage.haruk.in/s2000_smn.png",
"2079D": "https://storage.haruk.in/s2000_smn.png",
"2081D": "https://storage.haruk.in/s2000_smn.png",
// 2700
"2072D": "https://storage.haruk.in/s2700_asi.png",
"2078D": "https://storage.haruk.in/s2700_asi.png",
"2084D": "https://storage.haruk.in/s2700_asi.png",
"2075D": "https://storage.haruk.in/s2700_asi.png",
"2077D": "https://storage.haruk.in/s2700_asi.png",
"2083D": "https://storage.haruk.in/s2700_asi.png",
// ── 剣山 ───────────────────────────────────────────
"4002D": "https://storage.haruk.in/s185tu.png",
"4004D": "https://storage.haruk.in/s185tu.png",
"4006D": "https://storage.haruk.in/s185tu.png",
"4001D": "https://storage.haruk.in/s185tu.png",
"4003D": "https://storage.haruk.in/s185tu.png",
"4005D": "https://storage.haruk.in/s185tu.png",
"4007D": "https://storage.haruk.in/s185tu.png",
// ── よしのがわトロッコ ─────────────────────────────
"8452D": "https://storage.haruk.in/s185to_ai.png",
"8451D": "https://storage.haruk.in/s185to_ai.png",
// ── 岡山高松/琴平アントロ ──────────────────────────
"8176D": "https://storage.haruk.in/s32to4.png",
"8179D": "https://storage.haruk.in/s32to4.png",
"8277D": "https://storage.haruk.in/s32to4.png",
"8278D": "https://storage.haruk.in/s32to4.png",
// ── 千年ものがたり ─────────────────────────────────
"8021D": "https://storage.haruk.in/s185mm1.png",
"8022D": "https://storage.haruk.in/s185mm1.png",
// ── 夜明けものがたり ───────────────────────────────
"8082D": "https://storage.haruk.in/s185ym1.png",
"8083D": "https://storage.haruk.in/s185ym1.png",
"8073D": "https://storage.haruk.in/s185ym1.png",
"8074D": "https://storage.haruk.in/s185ym1.png",
// ── ラ・マルどこまでも ─────────────────────────────
"9253M": "https://storage.haruk.in/w213w.png",
"9256M": "https://storage.haruk.in/w213w.png",
// ── 貨物 ───────────────────────────────────────────
"74": "https://storage.haruk.in/ef210a.png",
"75": "https://storage.haruk.in/ef210a.png",
"70": "https://storage.haruk.in/ef210a.png",
"71": "https://storage.haruk.in/ef210a.png",
"73": "https://storage.haruk.in/ef210a.png",
"76": "https://storage.haruk.in/ef210a.png",
"3070": "https://storage.haruk.in/ef210a.png",
"3071": "https://storage.haruk.in/ef210a.png",
"3072": "https://storage.haruk.in/ef210a.png",
"3073": "https://storage.haruk.in/ef210a.png",
"3076": "https://storage.haruk.in/ef210a.png",
"3077": "https://storage.haruk.in/ef210a.png",
"3078": "https://storage.haruk.in/ef210a.png",
"3079": "https://storage.haruk.in/ef210a.png",
"8070": "https://storage.haruk.in/ef210a.png",
"8071": "https://storage.haruk.in/ef210a.png",
"8072": "https://storage.haruk.in/ef210a.png",
"8077": "https://storage.haruk.in/ef210a.png",
// ── 伊予灘ものがたり ───────────────────────────────
"8091D": "https://storage.haruk.in/s185iyor.png",
"8093D": "https://storage.haruk.in/s185iyor.png",
"8092D": "https://storage.haruk.in/s185iyoy.png",
"8094D": "https://storage.haruk.in/s185iyoy.png",
// ── 高徳線・徳島線・牟岐線・鳴門線 キハ40・47 ────
"4303D": "https://storage.haruk.in/s40.png",
"371D": "https://storage.haruk.in/s40.png",
"316D": "https://storage.haruk.in/s40.png",
"362D": "https://storage.haruk.in/s40.png",
"4376D": "https://storage.haruk.in/s40.png",
"951D": "https://storage.haruk.in/s40.png",
"953D": "https://storage.haruk.in/s40.png",
"955D": "https://storage.haruk.in/s40.png",
"973D": "https://storage.haruk.in/s40.png",
"975D": "https://storage.haruk.in/s40.png",
"977D": "https://storage.haruk.in/s40.png",
"979D": "https://storage.haruk.in/s40.png",
"981D": "https://storage.haruk.in/s40.png",
"950D": "https://storage.haruk.in/s40.png",
"968D": "https://storage.haruk.in/s40.png",
"970D": "https://storage.haruk.in/s40.png",
"972D": "https://storage.haruk.in/s40.png",
"974D": "https://storage.haruk.in/s40.png",
"976D": "https://storage.haruk.in/s40.png",
"980D": "https://storage.haruk.in/s40.png",
"982D": "https://storage.haruk.in/s40.png",
// ── 1000形 ─────────────────────────────────────────
"4311D": "https://storage.haruk.in/s1000.png",
"363D": "https://storage.haruk.in/s1000.png",
"356D": "https://storage.haruk.in/s1000.png",
"4374D": "https://storage.haruk.in/s1000.png",
"433D": "https://storage.haruk.in/s1000.png",
"4447D": "https://storage.haruk.in/s1000.png",
"451D": "https://storage.haruk.in/s1000.png",
"450D": "https://storage.haruk.in/s1000.png",
"4458D": "https://storage.haruk.in/s1000.png",
"474D": "https://storage.haruk.in/s1000.png",
// ── 1200形 ─────────────────────────────────────────
"4301D": "https://storage.haruk.in/s1200n.png",
"4327D": "https://storage.haruk.in/s1200n.png",
"4329D": "https://storage.haruk.in/s1200n.png",
"4343D": "https://storage.haruk.in/s1200n.png",
"353D": "https://storage.haruk.in/s1200n.png",
"355D": "https://storage.haruk.in/s1200n.png",
"367D": "https://storage.haruk.in/s1200n.png",
"310D": "https://storage.haruk.in/s1200n.png",
"4326D": "https://storage.haruk.in/s1200n.png",
"4334D": "https://storage.haruk.in/s1200n.png",
"4342D": "https://storage.haruk.in/s1200n.png",
"358D": "https://storage.haruk.in/s1200n.png",
"364D": "https://storage.haruk.in/s1200n.png",
"4453D": "https://storage.haruk.in/s1200n.png",
"4455D": "https://storage.haruk.in/s1200n.png",
"4457D": "https://storage.haruk.in/s1200n.png",
"463D": "https://storage.haruk.in/s1200n.png",
"475D": "https://storage.haruk.in/s1200n.png",
"477D": "https://storage.haruk.in/s1200n.png",
"485D": "https://storage.haruk.in/s1200n.png",
"4430D": "https://storage.haruk.in/s1200n.png",
"434D": "https://storage.haruk.in/s1200n.png",
"438D": "https://storage.haruk.in/s1200n.png",
"4460D": "https://storage.haruk.in/s1200n.png",
"4464D": "https://storage.haruk.in/s1200n.png",
"4466D": "https://storage.haruk.in/s1200n.png",
"478D": "https://storage.haruk.in/s1200n.png",
"484D": "https://storage.haruk.in/s1200n.png",
"957D": "https://storage.haruk.in/s1200n.png",
"4959D": "https://storage.haruk.in/s1200n.png",
"4963D": "https://storage.haruk.in/s1200n.png",
"4967D": "https://storage.haruk.in/s1200n.png",
"4971D": "https://storage.haruk.in/s1200n.png",
"952D": "https://storage.haruk.in/s1200n.png",
"4954D": "https://storage.haruk.in/s1200n.png",
"4958D": "https://storage.haruk.in/s1200n.png",
"4962D": "https://storage.haruk.in/s1200n.png",
"4966D": "https://storage.haruk.in/s1200n.png",
// ── 半定期臨時 ─────────────────────────────────────
"9174M": "https://storage.haruk.in/s5001.png",
"9395D": "https://storage.haruk.in/s1500.png",
};
/** 正規表現パターンマッチング (完全一致で未ヒットの場合に評価) */
export const TRAIN_ICON_REGEX: Array<{ pattern: string; url: string }> = [
// 高徳線 普通
{ pattern: "^(4|5)3\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
{ pattern: "^3\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
// 徳島線 普通
{ pattern: "^(4|5)4\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
{ pattern: "^4\\d\\dD$", url: "https://storage.haruk.in/s1500.png" },
// 鳴門線 普通
{
pattern: "^(4|5)9(5|6|7|8)\\dD$",
url: "https://storage.haruk.in/s1500.png",
},
{ pattern: "^9(5|6|7|8)\\dD$", url: "https://storage.haruk.in/s1500.png" },
];

View File

@@ -1,35 +0,0 @@
/**
* 列車種別ごとの表示設定
*
* - typeColor : nameReplace で使う種別ラベルの文字色
* - borderColor: setNewTrainItem で使う枠線色
* - bgColor : setNewTrainItem で使う背景色
* - label : 種別ラベル文字列
* - isWanman : ワンマン列車かどうか
*/
export interface TrainTypeConfig {
label: string;
typeColor: string;
borderColor: string;
bgColor: string;
isWanman: boolean;
}
export const TRAIN_TYPE_CONFIG: Record<string, TrainTypeConfig> = {
Normal: { label: "普通", typeColor: "black", borderColor: "black", bgColor: "#ffffffcc", isWanman: false },
OneMan: { label: "普通", typeColor: "black", borderColor: "black", bgColor: "#ffffffcc", isWanman: true },
Rapid: { label: "快速", typeColor: "rgba(0, 140, 255, 1)", borderColor: "rgba(0, 140, 255, 1)", bgColor: "#ffffffcc", isWanman: false },
OneManRapid: { label: "快速", typeColor: "rgba(0, 140, 255, 1)", borderColor: "rgba(0, 140, 255, 1)", bgColor: "#ffffffcc", isWanman: true },
LTDEXP: { label: "特急", typeColor: "red", borderColor: "red", bgColor: "#ffffffcc", isWanman: false },
NightLTDEXP: { label: "寝台特急", typeColor: "#d300b0ff", borderColor: "#d300b0ff", bgColor: "#ffffffcc", isWanman: false },
SPCL: { label: "臨時", typeColor: "#008d07ff", borderColor: "#008d07ff", bgColor: "#ffffffcc", isWanman: false },
SPCL_Normal: { label: "臨時", typeColor: "#008d07ff", borderColor: "#008d07ff", bgColor: "#ffffffcc", isWanman: false },
SPCL_Rapid: { label: "臨時快速", typeColor: "rgba(0, 81, 255, 1)", borderColor: "#0051ffff", bgColor: "#ffffffcc", isWanman: false },
SPCL_EXP: { label: "臨時特急", typeColor: "#a52e2eff", borderColor: "#a52e2eff", bgColor: "#ffffffcc", isWanman: false },
Party: { label: "団体臨時", typeColor: "#ff7300ff", borderColor: "#ff7300ff", bgColor: "#ffd0a9ff", isWanman: false },
Freight: { label: "貨物", typeColor: "#00869ecc", borderColor: "#00869ecc", bgColor: "#c7c7c7cc", isWanman: false },
Forwarding: { label: "回送", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
Trial: { label: "試運転", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
Construction: { label: "工事", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
FreightForwarding: { label: "単機回送", typeColor: "#727272cc", borderColor: "#727272cc", bgColor: "#c7c7c7cc", isWanman: false },
};

View File

@@ -39,8 +39,6 @@ const initialState = {
setTrainMenu: (e) => {},
updatePermission: false,
setUpdatePermission: (e) => {},
/** 各情報ソースの利用権限 */
dataSourcePermission: { unyohub: false } as { unyohub: boolean },
injectJavascript: "",
};
@@ -64,23 +62,20 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
const [stationMenu, setStationMenu] = useState<boolType>(undefined);
const [LoadError, setLoadError] = useState(false);
//更新権限所有確認・情報ソース別利用権限(将来ロールが増えたらここに足す)
//更新権限所有確認
const [updatePermission, setUpdatePermission] = useState(false);
const [dataSourcePermission, setDataSourcePermission] = useState<{ unyohub: boolean }>({ unyohub: false });
useEffect(() => {
if (!expoPushToken) return;
fetch(
"https://n8n.haruk.in/webhook/data-edit-permission?token=" + expoPushToken
)
.then((res) => res.json())
.then((res) => {
const role: string = res.data ?? "";
setUpdatePermission(role === "administrator");
setDataSourcePermission({
unyohub: role === "administrator" || role === "unyoHubEditor",
});
})
.catch(() => {});
if (res.data == true) {
setUpdatePermission(true);
} else {
setUpdatePermission(false);
}
});
}, [expoPushToken]);
//列車情報表示関連
@@ -95,19 +90,15 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
//GUIデザインベース
const [uiSetting, setUiSetting] = useState("tokyo");
//列車運用hub
const [useUnyohubSetting, setUseUnyohubSetting] = useState("false");
//地図表示テキスト
const injectJavascript = injectJavascriptData({
const injectJavascript = injectJavascriptData(
mapSwitch,
iconSetting,
stationMenu,
trainMenu,
uiSetting,
useUnyohub: useUnyohubSetting,
});
uiSetting
);
useEffect(() => {
//列車アイコンスイッチ
@@ -120,8 +111,6 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
ASCore({ k: STORAGE_KEYS.TRAIN_SWITCH, s: setTrainMenu, d: "true", u: true });
//GUIデザインベーススイッチ
ASCore({ k: STORAGE_KEYS.UI_SETTING, s: setUiSetting, d: "tokyo", u: true });
//列車運用hubスイッチ
ASCore({ k: STORAGE_KEYS.USE_UNYOHUB, s: setUseUnyohubSetting, d: "false", u: true });
}, []);
return (
@@ -147,7 +136,6 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
setTrainMenu,
updatePermission,
setUpdatePermission,
dataSourcePermission,
injectJavascript,
}}
>

View File

@@ -1,116 +0,0 @@
import { useState, useEffect } from "react";
import { AS } from "../storageControl";
import { STORAGE_KEYS } from "@/constants";
import { API_ENDPOINTS } from "@/constants";
import type { UnyohubResponse, UnyohubData } from "@/types/unyohub";
type UnyohubHook = {
/** 列車運用hub使用設定 */
useUnyohub: boolean;
/** 列車運用hubデータ */
unyohubData: UnyohubResponse;
/** 指定した列番の運用情報を文字列で取得(後方互換) */
getUnyohubByTrainNumber: (trainNumber: string) => string | null;
/** 指定した列番に紐づく UnyohubData の配列を取得 */
getUnyohubEntriesByTrainNumber: (trainNumber: string) => UnyohubData[];
/** 列車運用hub使用設定を更新 */
setUseUnyohub: (value: boolean) => void;
};
export const useUnyohub = (): UnyohubHook => {
const [useUnyohub, setUseUnyohubState] = useState(false);
const [unyohubData, setUnyohubData] = useState<UnyohubResponse>([]);
// 初期読み込み
useEffect(() => {
AS.getItem(STORAGE_KEYS.USE_UNYOHUB).then((value) => {
setUseUnyohubState(value === true || value === "true");
});
AS.getItem(STORAGE_KEYS.UNYOHUB_DATA).then((value) => {
if (value) {
try {
setUnyohubData(JSON.parse(value as string));
} catch (e) {
console.error("Failed to parse unyohub data", e);
}
}
});
}, []);
// データ更新処理
useEffect(() => {
if (!useUnyohub) return;
const fetchUnyohubData = async () => {
try {
// キャッシュバスティング用にタイムスタンプを追加
const cacheBuster = '?_=' + Date.now();
const response = await fetch(API_ENDPOINTS.UNYOHUB_DATA + cacheBuster);
const data = await response.json();
setUnyohubData(data);
await AS.setItem(STORAGE_KEYS.UNYOHUB_DATA, JSON.stringify(data));
} catch (error) {
console.error("Failed to fetch unyohub data", error);
}
};
fetchUnyohubData();
// 10分ごとにデータを更新
const interval = setInterval(fetchUnyohubData, 10 * 60 * 1000);
return () => clearInterval(interval);
}, [useUnyohub]);
// 列番から運用番号を取得複数ある場合はposition_forward順
const getUnyohubByTrainNumber = (trainNumber: string): string | null => {
if (!useUnyohub || unyohubData.length === 0) return null;
const foundUnyos: Array<{ formations: string; position_forward: number; position_rear: number }> = [];
for (const unyo of unyohubData) {
if (!unyo.trains) continue;
const found = unyo.trains.find(train => train.train_number === trainNumber);
if (found) {
foundUnyos.push({
formations: unyo.formations,
position_forward: found.position_forward,
position_rear: found.position_rear,
});
}
}
if (foundUnyos.length === 0) return null;
// position_forward順にソート
foundUnyos.sort((a, b) => a.position_forward - b.position_forward);
// 「運用番号(編成位置)」の形式で結合
return foundUnyos
.map(u => `${u.formations}(${u.position_forward}-${u.position_rear})`)
.join(', ');
};
// 列番に紐づく UnyohubData エントリをすべて取得
const getUnyohubEntriesByTrainNumber = (trainNumber: string): UnyohubData[] => {
if (!useUnyohub || unyohubData.length === 0) return [];
return unyohubData.filter(
(unyo) => unyo.trains?.some((t) => t.train_number === trainNumber)
);
};
// 設定を更新
const setUseUnyohub = (value: boolean) => {
setUseUnyohubState(value);
AS.setItem(STORAGE_KEYS.USE_UNYOHUB, value.toString());
};
return {
useUnyohub,
unyohubData,
getUnyohubByTrainNumber,
getUnyohubEntriesByTrainNumber,
setUseUnyohub,
};
};

View File

@@ -1,35 +0,0 @@
/**
* 列車運用hub APIのデータ型定義
*/
export type UnyohubTrain = {
train_number: string;
line_id: string;
first_departure_time: string;
final_arrival_time: string;
starting_station: string;
terminal_station: string;
position_forward: number;
position_rear: number;
direction: string;
};
export type UnyohubData = {
formations: string;
posts_count: number;
from_beginner: boolean;
trains: UnyohubTrain[];
starting_location: string;
starting_track: string;
starting_time: string;
terminal_location: string;
terminal_track: string;
ending_time: string;
car_count: number;
min_car_count: number;
max_car_count: number;
main_color: string;
comment: string | null;
};
export type UnyohubResponse = UnyohubData[];

View File

@@ -18,13 +18,13 @@ const SE_MAPPING: Record<string, SeStringResult> = {
"頃編": ["頃", "community"],
// 運休系
"休編": ["運休", "community"], // 後方互換性のため残す
"休発": ["出発", "normal"],
"休着": ["到着", "normal"],
"休発編": ["出発", "community"],
"休着編": ["到着", "community"],
"通休編": ["通過", "community"],
"通発休編": ["出発", "community"],
"通着休編": ["到着", "community"],
"休発": ["出発(運休)", "normal"],
"休着": ["到着(運休)", "normal"],
"休発編": ["出発(運休)", "community"],
"休着編": ["到着(運休)", "community"],
"通休編": ["通過(運休)", "community"],
"通発休編": ["出発(運休)", "community"],
"通着休編": ["到着(運休)", "community"],
};
/**