38 Commits

Author SHA1 Message Date
harukin-expo-dev-env
834ad2bd41 feat: implement elesite data source integration and permission handling 2026-03-25 01:56:12 +00:00
harukin-expo-dev-env
83ca18f2c7 feat: add detailed changelog for version 7.0beta with feature enhancements and improvements 2026-03-25 01:44:54 +00:00
harukin-expo-dev-env
baacfd5855 refactor: remove LiveActivityButton from EachTrainInfoCore component 2026-03-25 01:30:57 +00:00
harukinMBP
066317bbc8 feat: add SpotSign component for tourist attractions 2026-03-25 10:22:12 +09:00
harukinMBP
0bcb03f833 refactor: remove live notification functionality from FixedStation and FixedTrain components 2026-03-25 02:07:43 +09:00
harukinMBP
9037d21237 fix: TypeScript build errors (143 to 47) 2026-03-25 01:44:29 +09:00
harukin-expo-dev-env
4789543573 Merge origin/develop into develop and resolve conflicts 2026-03-24 16:11:08 +00:00
harukin-expo-dev-env
5345dca95f Merge branch 'feature/fix-some' into develop 2026-03-24 16:07:30 +00:00
harukin-expo-dev-env
b1a8a4c98f feat: update iOS build number to 59 in app.json 2026-03-22 17:11:15 +00:00
harukin-expo-dev-env
50fd2ece64 Merge commit 'f972d2b719eb9d26a738761800bc1901c573c800' into develop 2026-03-22 17:10:00 +00:00
harukin-expo-dev-env
f972d2b719 Merge commit 'deb24caaa2472ac2ec7715c96f3d0b69ebb96cde' into feature/migration-expo-upgrade 2026-03-22 17:09:51 +00:00
harukin-expo-dev-env
deb24caaa2 feat: add station progress tracking and notification updates for train follow feature 2026-03-22 17:09:19 +00:00
harukinMBP
5515f42415 feat: add Live Activity button for train tracking and update iOS deployment target 2026-03-23 02:07:24 +09:00
harukin-expo-dev-env
fbfb83fa34 feat: add live activity notifications for train tracking and station locking
- Implemented ExpoLiveActivity module for Android to manage live notifications.
- Added foreground service for train tracking and station locking notifications.
- Updated app permissions to include POST_NOTIFICATIONS.
- Enhanced FixedStation and FixedTrain components to support live notifications.
- Introduced new notification builders for train and station activities.
- Updated useCurrentTrain and useNotifications hooks to manage live notification state.
- Added notification channel for live tracking in Android.
2026-03-22 16:15:48 +00:00
harukinMBP
8eb49f57d6 fix: update activity state handling to use ActivityContent with nil staleDate 2026-03-22 23:09:13 +09:00
harukinMBP
c5d4dc3b65 feat: add automatic Live Activity updates for train position and station lock in TrainDataView and StationDiagramView 2026-03-22 23:03:07 +09:00
harukinMBP
7f2480bc01 feat: add Live Activity support for station locking in StationDiagramView 2026-03-22 23:01:00 +09:00
harukinMBP
b8372e5087 feat: add hooks for managing Live Activities for station locking and train following 2026-03-22 22:51:58 +09:00
harukinMBP
75c07f013d feat: add Live Activities support for train tracking and station locking 2026-03-22 22:49:05 +09:00
harukin-expo-dev-env
46bfea4e13 feat: add androidGoogleMapsApiKey to app.json configuration 2026-03-22 13:17:53 +00:00
harukin-expo-dev-env
5a2dc8c6a8 feat: add appleTeamId to iOS configuration in app.json 2026-03-22 11:48:59 +00:00
harukin-expo-dev-env
06650d014a feat(widget): add Shortcut, Delay Info, and Felica Balance widgets
- Implemented ShortcutWidget for quick access to app features with customizable shortcuts.
- Added DelayInfoWidget to display train delay information fetched from a remote endpoint.
- Created FelicaBalanceWidget to show the balance of Felica-compatible IC cards.
- Introduced OperationInfoWidget for displaying train operation status.
- Set up shared data handling for Felica snapshots between the main app and widget.
- Configured widget assets and entitlements for proper functionality.
- Updated Info.plist and expo-target.config.js for widget deployment.
2026-03-22 11:45:58 +00:00
harukin-expo-dev-env
45feeece58 feat: FeliCa対応の可用性チェック機能を追加 2026-03-22 10:13:36 +00:00
harukinMBP
385c2d8b86 Update app.json build number to 58 and refine NFC error handling in ExpoFelicaReaderModule 2026-03-22 18:56:13 +09:00
harukinMBP
41cc70086a Implement NFC scanning functionality in ExpoFelicaReader module 2026-03-21 22:04:55 +09:00
harukinMBP
98c71127aa Fix .gitignore: re-include modules/**/ios/ for EAS build, remove debug code 2026-03-21 20:13:34 +09:00
harukinMBP
d049d3b07d Debug: patch resolveCommand.js and apple.js with logging, add pod fallback 2026-03-21 20:03:40 +09:00
harukinMBP
8484f15092 Enhanced autolinking debug: use external JS file 2026-03-21 19:46:25 +09:00
harukinMBP
1afa5e4377 Add Podfile debug plugin to trace autolinking resolve 2026-03-21 19:33:22 +09:00
harukinMBP
9f6d86b8b6 Fix ExpoFelicaReader module: restore full definition, update diagnostics 2026-03-21 17:54:54 +09:00
harukinMBP
72e7d63bd7 Add expo-felica-reader to dependencies for EAS build autolinking 2026-03-21 17:54:39 +09:00
harukin-expo-dev-env
b243439e78 Refactor code structure for improved readability and maintainability 2026-03-20 07:14:58 +00:00
harukin-expo-dev-env
65f3b2a877 fix: ダークモード - 設定リスト・お気に入り並び替え・SNSリストの表示修正
- SettingTopPage: SettingList の containerStyle を直接設定して背景色を適用
- FavoiliteSettingsItem: 駅名テキストと並び替えアイコンに color: colors.text を追加
- SocialMenu: SNS一覧の ListItem に containerStyle と ListItem.Title の色を追加

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-19 08:46:14 +00:00
harukin-expo-dev-env
925d162f26 feat: iPad サポートを有効化
app.json の supportsTablet を true に変更

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 08:44:36 +00:00
harukin-expo-dev-env
731fe504c6 perf: dev client で expo-updates をスキップ
__DEV__ 時は checkForUpdateAsync() が不要なため早期リターン

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 08:38:10 +00:00
harukin-expo-dev-env
942ec395f1 perf: metro transformer に experimentalImportSupport を復元
lazyImports: true と組み合わせて動作するオプション。
resolver への誤設定は除外し transformer のみに適用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 07:42:55 +00:00
harukin-expo-dev-env
306cf6882e chore: 不要なビルドファイル build-1773815539430.apk を削除 2026-03-18 07:12:45 +00:00
harukin-expo-dev-env
468bb4633a chore: metro.config.js から deprecated な experimentalImportSupport を削除
SDK 55 の Metro では不要。resolver への設定は誤り。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 07:11:15 +00:00
46 changed files with 592 additions and 155 deletions

View File

@@ -107,7 +107,7 @@ export function MenuPage() {
return (
<Stack.Navigator
id={null}
screenOptions={{ contentStyle: { backgroundColor: bgColor } }}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
>
<Stack.Screen
name="menu"

View File

@@ -19,7 +19,7 @@ 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";
@@ -59,7 +59,7 @@ export const Top = () => {
return (
<Stack.Navigator
id={null}
screenOptions={{ contentStyle: { backgroundColor: bgColor } }}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
>
<Stack.Screen
name="Apps"

View File

@@ -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",
},
});

View File

@@ -124,7 +124,6 @@ export const EachTrainInfoCore = ({
} else {
SheetManager.hide("EachTrainInfo").then(() => {
setTimeout(() => {
// @ts-expect-error - SheetManager payload type is too restrictive
SheetManager.show("EachTrainInfo", { payload });
}, 200);
});

View File

@@ -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]);

View File

@@ -28,7 +28,7 @@ export const JRSTraInfo = () => {
const { colors, fixed } = useThemeColors();
const timeData = dayjs(getTime).format("HH:mm");
const actionSheetRef = useRef(null);
const scrollHandlers = useScrollHandlers("scrollview-1", actionSheetRef);
const scrollHandlers = useScrollHandlers();
const insets = useSafeAreaInsets();
const viewShot = useRef(null);
@@ -59,7 +59,8 @@ export const JRSTraInfo = () => {
<View
style={{
backgroundColor: fixed.primary,
borderTopRadius: 5,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderColor: colors.border,
borderWidth: 1,
}}
@@ -104,7 +105,7 @@ export const JRSTraInfo = () => {
}}
/>
</View>
<ScrollView {...scrollHandlers}>
<ScrollView {...scrollHandlers as any}>
<View
style={{
padding: 10,

View File

@@ -30,7 +30,8 @@ export const Social = () => {
<View
style={{
backgroundColor: fixed.primary,
borderTopRadius: 5,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderColor: "dark",
borderWidth: 1,
height: "100%",
@@ -65,6 +66,7 @@ export const Social = () => {
<ListItem
bottomDivider
onPress={() => Linking.openURL("tel:0570-00-4592")}
// @ts-ignore: TouchableScale props passed through Component
friction={90}
tension={100}
activeScale={0.95}
@@ -80,7 +82,8 @@ export const Social = () => {
0570-00-45928:0020:00
</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron color={fixed.textOnPrimary} />
{/* @ts-ignore: ListItem.Chevron doesn't expose color prop */}
<ListItem.Chevron color={"white"} />
</ListItem>
<ScrollView
style={{
@@ -150,6 +153,7 @@ 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} //

View File

@@ -33,7 +33,8 @@ export const SpecialTrainInfo: FC<props> = ({ payload }) => {
<View
style={{
backgroundColor: fixed.primary,
borderTopRadius: 5,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderColor: "dark",
borderWidth: 1,
}}

View File

@@ -29,7 +29,7 @@ export const StationDeteilView = (props) => {
const { currentStation, navigate, onExit, goTo, useShow } = props.payload;
const { width } = useWindowDimensions();
const { busAndTrainData } = useBusAndTrainData();
const [trainBus, setTrainBus] = useState();
const [trainBus, setTrainBus] = useState<any>(undefined);
const { colors } = useThemeColors();
useEffect(() => {
@@ -38,7 +38,7 @@ export const StationDeteilView = (props) => {
(d) => d.name === currentStation[0].Station_JP
);
if (data.length == 0) {
setTrainBus();
setTrainBus(undefined);
}
setTrainBus(data[0]);
}, [currentStation, busAndTrainData]);
@@ -72,7 +72,8 @@ export const StationDeteilView = (props) => {
key={currentStation}
style={{
backgroundColor: colors.sheetBackground,
borderTopRadius: 5,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderColor: "dark",
borderWidth: 1,
}}

View File

@@ -60,7 +60,8 @@ export const TrainIconUpdate = () => {
<View
style={{
backgroundColor: fixed.primary,
borderTopRadius: 5,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
borderColor: "dark",
borderWidth: 1,
}}

View File

@@ -0,0 +1,14 @@
import { SheetDefinition } from "react-native-actions-sheet";
declare module "react-native-actions-sheet" {
interface Sheets {
EachTrainInfo: SheetDefinition<{ payload: any }>;
JRSTraInfo: SheetDefinition;
StationDetailView: SheetDefinition<{ payload: any }>;
TrainMenuLineSelector: SheetDefinition<{ payload: any }>;
TrainIconUpdate: SheetDefinition<{ payload: any }>;
SpecialTrainInfo: SheetDefinition<{ payload: any }>;
Social: SheetDefinition;
TrainDataSources: SheetDefinition;
}
}

View File

@@ -1,4 +1,4 @@
import { registerSheet } from "react-native-actions-sheet";
import { registerSheet, SheetDefinition } from "react-native-actions-sheet";
import { EachTrainInfo } from "./EachTrainInfo";
import { JRSTraInfo } from "./JRSTraInfo";
import { StationDeteilView } from "./StationDeteilView";
@@ -18,3 +18,16 @@ registerSheet("Social", Social);
registerSheet("TrainDataSources", TrainDataSources);
export {};
declare module "react-native-actions-sheet" {
interface Sheets {
EachTrainInfo: SheetDefinition<{ payload: any }>;
JRSTraInfo: SheetDefinition;
StationDetailView: SheetDefinition<{ payload: any }>;
TrainMenuLineSelector: SheetDefinition<{ payload: any }>;
TrainIconUpdate: SheetDefinition<{ payload: any }>;
SpecialTrainInfo: SheetDefinition<{ payload: any }>;
Social: SheetDefinition;
TrainDataSources: SheetDefinition;
}
}

View File

@@ -26,7 +26,7 @@ import { getStringConfig } from "@/lib/getStringConfig";
export const AllTrainDiagramView: FC = () => {
const { colors, fixed } = useThemeColors();
const { goBack, navigate } = useNavigation();
const { goBack, navigate } = useNavigation<any>();
const tabBarHeight = useBottomTabBarHeight();
const {
keyList,

View File

@@ -42,7 +42,7 @@ export function InfoWidget({ time, text }) {
justifyContent: "center",
alignItems: "center",
backgroundColor: "#0099CC",
width: "100%",
width: "100%" as any,
flexDirection: "row",
paddingTop: 10,
paddingBottom: 10,
@@ -78,7 +78,7 @@ export function InfoWidget({ time, text }) {
width: "match_parent",
height: "match_parent",
padding: 10,
}}
} as any}
>
{text ? (
<TextWidget

View File

@@ -105,7 +105,7 @@ function GridTile({
{sub !== undefined ? (
<TextWidget
text={sub}
style={{ color: subColor ?? "#555555", fontSize: 10, marginTop: 1 }}
style={{ color: (subColor ?? "#555555") as any, fontSize: 10, marginTop: 1 }}
/>
) : (
<TextWidget text="" style={{ fontSize: 1 }} />

View File

@@ -41,7 +41,7 @@ export function TraInfoEXWidget({ time, delayString }) {
justifyContent: "center",
alignItems: "center",
backgroundColor: "#0099CC",
width: "100%",
width: "100%" as any,
flexDirection: "row",
paddingTop: 10,
paddingBottom: 10,
@@ -77,7 +77,7 @@ export function TraInfoEXWidget({ time, delayString }) {
width: "match_parent",
height: "match_parent",
padding: 10,
}}
} as any}
>
{delayString ? (
delayString.map((d) => {

View File

@@ -28,7 +28,7 @@ import { FixedPositionBox } from "./Apps/FixedPositionBox";
export default function Apps() {
const { webview, fixedPosition, setFixedPosition } = useCurrentTrain();
const { height, width } = useWindowDimensions();
const { navigate } = useNavigation();
const { navigate } = useNavigation<any>();
const { isLandscape } = useDeviceOrientationChange();
const { top } = useSafeAreaInsets();
const handleLayout = () => {};
@@ -69,13 +69,11 @@ export default function Apps() {
navigate,
goTo: "Apps",
useShow: () => {
// @ts-expect-error - SheetManager payload type is too restrictive
SheetManager.show("StationDetailView", { payload });
},
onExit: () => SheetManager.hide("StationDetailView"),
};
setTimeout(() => {
// @ts-expect-error - SheetManager payload type is too restrictive
SheetManager.show("StationDetailView", { payload });
}, 50);
} else {

View File

@@ -44,8 +44,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
setFixedPosition,
fixedPositionSize,
setFixedPositionSize,
liveNotificationActive,
setLiveNotificationActive,
} = useCurrentTrain();
const { getStationDataFromId } = useStationList();
const { stationList } = useStationList();
@@ -117,6 +115,13 @@ export const FixedStation: FC<props> = ({ stationID }) => {
const [selectedTrain, setSelectedTrain] = useState<eachTrainDiagramType[]>(
[]
);
// ── Live Notification ──
const [liveNotifyId, setLiveNotifyId] = useState<string | null>(null);
const liveNotifyIdRef = useRef<string | null>(null);
const hasStartedRef = useRef(false);
const [tick, setTick] = useState(0);
useEffect(() => {
if (!trainTimeAndNumber) return () => {};
if (!currentTrain) return () => {};
@@ -128,12 +133,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
setSelectedTrain(data);
}, [trainTimeAndNumber, currentTrain, tick /*liveActivity periodic refresh*/]);
// ── Live Notification ──
const [liveNotifyId, setLiveNotifyId] = useState<string | null>(null);
const liveNotifyIdRef = useRef<string | null>(null);
const hasStartedRef = useRef(false);
const [tick, setTick] = useState(0);
useEffect(() => {
liveNotifyIdRef.current = liveNotifyId;
}, [liveNotifyId]);
@@ -149,7 +148,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
return () => {
if (liveNotifyIdRef.current) {
endStationLockActivity(liveNotifyIdRef.current).catch(() => {});
setLiveNotificationActive(false);
}
};
}, []);
@@ -225,7 +223,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
trains,
});
setLiveNotifyId(id);
setLiveNotificationActive(true);
} catch (e) {
console.warn('[LiveNotify] start error:', e);
hasStartedRef.current = false;

View File

@@ -46,8 +46,6 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
getPosition,
fixedPositionSize,
setFixedPositionSize,
liveNotificationActive,
setLiveNotificationActive,
} = useCurrentTrain();
const { mapSwitch } = useTrainMenu();
@@ -420,14 +418,13 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
return () => {
if (liveNotifyIdRef.current) {
endTrainFollowActivity(liveNotifyIdRef.current).catch(() => {});
setLiveNotificationActive(false);
}
};
}, []);
useEffect(() => {
if (!liveNotifyId || !train) return;
const delayNum = train.delay === "入線" ? 0 : parseInt(train.delay) || 0;
const delayNum = train.delay === "入線" ? 0 : parseInt(String(train.delay)) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus =
nextStationData[0]?.Station_JP === train.Pos ? "ただいま" : "次は";
@@ -463,7 +460,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) return;
}
const delayNum = train?.delay === "入線" ? 0 : parseInt(train?.delay) || 0;
const delayNum = train?.delay === "入線" ? 0 : parseInt(String(train?.delay)) || 0;
const delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
const positionStatus =
nextStationData[0]?.Station_JP === train?.Pos ? "ただいま" : "次は";
@@ -488,7 +485,6 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
currentStationIndex,
});
setLiveNotifyId(id);
setLiveNotificationActive(true);
} catch (e) {
console.warn('[LiveNotify] start error:', e);
}

View File

@@ -20,7 +20,7 @@ import { useTrainMenu } from "../../stateBox/useTrainMenu";
import { useStationList } from "../../stateBox/useStationList";
export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
const { webview, currentTrain } = useCurrentTrain();
const { navigate } = useNavigation();
const { navigate } = useNavigation<any>();
const { favoriteStation } = useFavoriteStation();
const { isLandscape } = useDeviceOrientationChange();
const isDark = useColorScheme() === "dark";
@@ -149,7 +149,6 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
return (
<WebView
useWebKit
ref={webview}
source={{ uri: "https://train.jr-shikoku.co.jp/sp.html" }}
originWhitelist={[

View File

@@ -14,7 +14,7 @@ export const FavoriteList: FC = () => {
const { colors, fixed } = useThemeColors();
const { favoriteStation } = useFavoriteStation();
const { webview } = useCurrentTrain();
const { navigate, addListener, goBack, canGoBack } = useNavigation();
const { navigate, addListener, goBack, canGoBack } = useNavigation<any>();
const { mapsStationData: stationData } = useTrainMenu();
const { getInjectJavascriptAddress } = useStationList();

View File

@@ -1,5 +1,5 @@
import Sign from "@/components/駅名表/Sign";
import { SpotSign } from "@/components/観光スポット看板/SpotSign";
import { SpotSign } from "@/components/SpotSign/SpotSign";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { AS } from "@/storageControl";
import {

View File

@@ -35,7 +35,7 @@ export const CarouselTypeChanger = ({
setMapMode: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const { fixedPosition, setFixedPosition } = useCurrentTrain();
const { navigate } = useNavigation();
const { navigate } = useNavigation<any>();
const { colors, fixed } = useThemeColors();
const isGpsFollowing = fixedPosition?.type === "nearestStation";
const containerRef = useRef(null);

View File

@@ -37,6 +37,8 @@ type DataSourceAccordionCardProps = {
linkLabel: string;
/** フッターリンク URL */
linkUrl: string;
/** 詳細ラベル */
detailLabel?: string;
};
const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
@@ -156,9 +158,9 @@ const ELESITE_FEATURES: Feature[] = [
/* ------------------------------------------------------------------ */
export const DataSourceSettings = () => {
const navigation = useNavigation();
const { updatePermission, dataSourcePermission } = useTrainMenu();
const { dataSourcePermission, updatePermission } = useTrainMenu();
const { colors, fixed } = useThemeColors();
const canAccess = updatePermission || Object.values(dataSourcePermission).some(Boolean);
const canUseElesite = updatePermission || dataSourcePermission.elesite;
const [useUnyohub, setUseUnyohub] = useState(false);
const [useElesite, setUseElesite] = useState(false);
@@ -177,6 +179,7 @@ export const DataSourceSettings = () => {
};
const handleToggleElesite = (value: boolean) => {
if (!canUseElesite) return;
setUseElesite(value);
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
};
@@ -191,13 +194,7 @@ export const DataSourceSettings = () => {
position: "left",
}}
/>
{!canAccess ? (
<View style={[styles.noPermissionContainer, { backgroundColor: colors.backgroundSecondary }]}>
<Text style={[styles.noPermissionText, { color: colors.textPrimary }]}></Text>
<Text style={[styles.noPermissionSubText, { color: colors.textSecondary }]}>Hubまたはアプリ管理者の権限が必要です</Text>
</View>
) : (
<ScrollView style={[styles.content, { backgroundColor: colors.backgroundSecondary }]} contentContainerStyle={styles.contentInner}>
<ScrollView style={[styles.content, { backgroundColor: colors.backgroundSecondary }]} contentContainerStyle={styles.contentInner}>
<Text style={[styles.sectionTitle, { color: colors.textTertiary }]}></Text>
<DataSourceAccordionCard
@@ -215,20 +212,22 @@ export const DataSourceSettings = () => {
linkUrl="https://unyohub.2pd.jp/railroad_shikoku/"
/>
<DataSourceAccordionCard
logo={ELESITE_LOGO_PNG}
accentColor="#44bb44"
title="えれサイト"
tagline="コミュニティによる列車運用情報サービス"
enabled={useElesite}
onToggle={handleToggleElesite}
description={
"えれサイトは、鉄道運用情報を共有するためのサイトです。皆様からの投稿を通じて、鉄道運行に関する情報を共有するサイトです。JR 四国の特急・普通列車を中心に対応しています。\n\nデータがある列車では地図上に緑色の「E」バッジが表示され、列車情報画面の編成表示も更新されます。"
}
features={ELESITE_FEATURES}
linkLabel="elesite-next.com を開く"
linkUrl="https://www.elesite-next.com/"
/>
{canUseElesite && (
<DataSourceAccordionCard
logo={ELESITE_LOGO_PNG}
accentColor="#44bb44"
title="えれサイト"
tagline="コミュニティによる列車運用情報サービス"
enabled={useElesite}
onToggle={handleToggleElesite}
description={
"えれサイトは、鉄道運用情報を共有するためのサイトです。皆様からの投稿を通じて、鉄道運行に関する情報を共有するサイトです。JR 四国の特急・普通列車を中心に対応しています。\n\nデータがある列車では地図上に緑色の「E」バッジが表示され、列車情報画面の編成表示も更新されます。"
}
features={ELESITE_FEATURES}
linkLabel="elesite-next.com を開く"
linkUrl="https://www.elesite-next.com/"
/>
)}
<View style={[styles.infoSection, { backgroundColor: colors.backgroundTertiary }]}>
<Text style={[styles.infoText, { color: colors.textCaution }]}>
@@ -238,32 +237,11 @@ export const DataSourceSettings = () => {
</Text>
</View>
</ScrollView>
)}
</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,

View File

@@ -40,7 +40,7 @@ export const LauncherIconSettings = () => {
<View
style={{
flexDirection: "cokumn",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#DDDDDD",

View File

@@ -74,7 +74,6 @@ export const LayoutSettings = ({
<SimpleSwitch
bool={usePDFView}
setBool={setUsePDFView}
color="red"
str="時刻表PDFをアプリの外で表示する"
/>
</SwitchArea>
@@ -110,7 +109,6 @@ export const LayoutSettings = ({
<SimpleSwitch
bool={trainPosition}
setBool={setTrainPosition}
color="red"
str="列車の現在地表示/ジャンプ"
/>
</SwitchArea>

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";
import { Asset } from "expo-asset";
import {
useAudioPlayer,
@@ -36,9 +35,8 @@ export const SettingTopPage = ({
}) => {
const { width } = useWindowDimensions();
const { expoPushToken } = useNotification();
const { updatePermission, dataSourcePermission } = useTrainMenu();
const { colors, fixed } = useThemeColors();
const navigation = useNavigation();
const navigation = useNavigation<any>();
// expo-asset でローカルパスを取得し、expo-audio に渡す
// (SDK 52 の expo-audio は file:// URI を正しく処理できないため prefix を除去)
@@ -95,9 +93,6 @@ export const SettingTopPage = ({
}
}, [previewPlayer]);
// admin またはいずれかのソース権限を持つ場合のみ表示
const canAccessDataSourceSettings =
updatePermission || Object.values(dataSourcePermission).some(Boolean);
return (
<View style={{ height: "100%", backgroundColor: fixed.primary }}>
<SheetHeaderItem title="アプリの設定画面" LeftItem={{
@@ -182,14 +177,12 @@ export const SettingTopPage = ({
navigation.navigate("setting", { screen: "LayoutSettings" })
}
/>
{canAccessDataSourceSettings && (
<SettingList
string="情報ソース設定"
onPress={() =>
navigation.navigate("setting", { screen: "DataSourceSettings" })
}
/>
)}
<SettingList
string="情報ソース設定"
onPress={() =>
navigation.navigate("setting", { screen: "DataSourceSettings" })
}
/>
<SettingList
string="アイコン設定"
@@ -256,10 +249,11 @@ export const SettingTopPage = ({
);
};
const SettingList = ({ string, onPress, disabled }) => {
const SettingList = ({ string, onPress, disabled = false }: { string: string; onPress: () => void; disabled?: boolean }) => {
const { colors } = useThemeColors();
return (
<ListItem
// @ts-ignore: TouchableScale props passed through Component
activeScale={0.95}
Component={TouchableScale}
bottomDivider

View File

@@ -34,7 +34,7 @@ export const SheetHeaderItem: FC<Props> = (props) => {
type SideItemProps = {
onPress: () => void;
title: string;
position: "left" | "right";
position?: "left" | "right";
};
const SideItem: FC<SideItemProps> = ({ onPress, title, position }) => {
const { fixed } = useThemeColors();

View File

@@ -11,7 +11,18 @@ export const SwitchArea = ({
trueText,
falseValue = false,
trueValue = true,
children,
children = null,
}: {
str: any;
bool: any;
setBool: any;
falseImage: any;
trueImage: any;
falseText: any;
trueText: any;
falseValue?: any;
trueValue?: any;
children?: any;
}) => {
const { colors } = useThemeColors();
return (

View File

@@ -15,8 +15,8 @@ import { useThemeColors } from "@/lib/theme";
export default function TrainMenu({ style }) {
const { fixed } = useThemeColors();
const { webview } = useCurrentTrain();
const mapRef = useRef();
const { navigate, goBack } = useNavigation();
const mapRef = useRef<any>(null);
const { navigate, goBack } = useNavigation<any>();
const [stationPin, setStationPin] = useState([]);
const {
selectedLine,
@@ -81,7 +81,7 @@ export default function TrainMenu({ style }) {
style={{
flexDirection: "row",
position: "absolute",
width: "100vw",
width: "100%" as any,
bottom: 0,
flex: 1,
}}

View File

@@ -76,7 +76,6 @@ export const EachData: FC<Props> = (props) => {
openStationACFromEachTrainInfo,
from: "LED",
};
// @ts-expect-error - SheetManager payload type is too restrictive
SheetManager.show("EachTrainInfo", { payload });
};

View File

@@ -5,7 +5,7 @@ import { useThemeColors } from "@/lib/theme";
export const SwitchBox = (props) => {
const { value, setValue, setKey, title } = props;
const { fixed } = useThemeColors();
const textStyle = {
const textStyle: any = {
alignItems: "center",
alignContent: "center",
textAlign: "center",

View File

@@ -5,7 +5,7 @@ type Props = {
progress: number;
};
export const LottieDelayView: FC<Props> = ({ progress }) => {
const lottieRef = useRef<LottieView>();
const lottieRef = useRef<LottieView>(null);
const [progressState, setProgressState] = useState(undefined);
useEffect(() => {
if (progress == 0) {

View File

@@ -141,7 +141,7 @@ export default function Sign(props) {
borderWidth: 0,
},
: {
position: "absolute",
position: "absolute" as const,
bottom: "8%",
left: "0%",
width: "100%",
@@ -149,7 +149,7 @@ export default function Sign(props) {
backgroundColor: fixed.primary,
},
B: {
position: "absolute",
position: "absolute" as const,
bottom: "0%",
left: "0%",
width: "100%",
@@ -157,28 +157,28 @@ export default function Sign(props) {
backgroundColor: "#454545",
},
JRStyle: {
position: "absolute",
position: "absolute" as const,
top: "2%",
left: "2%",
fontWeight: "bold",
fontWeight: "bold" as const,
fontSize: parseInt("25%"),
color: lightColors.textAccent,
},
: {
position: "absolute",
position: "absolute" as const,
bottom: "8%",
height: "27%",
width: "100%",
alignItems: "center",
flexDirection: "column",
alignItems: "center" as const,
flexDirection: "column" as const,
},
B: {
position: "absolute",
position: "absolute" as const,
bottom: "0%",
height: "26%",
width: "100%",
alignItems: "center",
flexDirection: "column",
alignItems: "center" as const,
flexDirection: "column" as const,
},
};
@@ -222,9 +222,9 @@ export default function Sign(props) {
</TouchableOpacity>
)}
<Text style={styleSheet.JRStyle}>JR</Text>
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"]} />
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"]}>
<Text style={styleSheet.JRStyle as any}>JR</Text>
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"] as any} />
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"] as any}>
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
</View>
<AddressText {...{ currentStation: currentStationData, isMatsuyama }} />

View File

@@ -13,7 +13,7 @@ type props = {
export const StationNumberMaker: FC<props> = (props) => {
const { currentStation, useEach = false, singleSize } = props;
const { width } = useWindowDimensions();
const getTop = (array: number[], index: number) => {
const getTop = (array: StationProps[], index: number) => {
if (array.length == 1) return 20;
else if (index == 0) return 5;
else if (index == 1) return 35;

View File

@@ -0,0 +1,89 @@
# 6.2.1.1 -> 7.0beta 更新ログ(機能説明フォーカス版)
## このバージョンで何が良くなったか
7.0beta では、列車情報アプリとしての実用性を高めるために、外部データ連携・通知/追跡・FeliCa・ウィジェット・操作性をまとめて強化しました。
特に「情報の鮮度」「追跡の継続性」「日常利用のしやすさ」に効く改善が多いアップデートです。
---
## 1. 鉄道運用HubUnyohub連携の強化
### 実装/更新された機能
- 外部データ連携の表示品質を向上。
- 最終更新時刻の表示や鮮度判定stale判定を追加。
- 情報ソース統合表示を改善し、データの信頼性を判断しやすく調整。
### ユーザーにとっての効果
- 古い情報を見分けやすくなり、表示内容の判断ミスを減らせます。
- ソース差異による混乱が減り、情報確認がスムーズになります。
## 2. 情報ソース設定と導線の整理
### 実装/更新された機能
- 情報ソース管理の共通化を進め、設定と表示の一貫性を改善。
- 設定画面の導線を整理し、利用環境に合わせた表示制御を適用。
- Deep Link/通知タップ時の遷移処理を見直し。
### ユーザーにとっての効果
- 設定変更後の挙動が分かりやすくなります。
- 起動時や通知タップ時の意図しない画面遷移が起きにくくなります。
## 3. 列車追跡通知・Live Activityの再設計
### 実装/更新された機能
- 列車追跡と駅固定の通知/Live Activity基盤を強化。
- 自動更新、次駅表示、進捗表示の精度を改善。
- バックグラウンド中も追跡更新が継続しやすい構成へ調整。
- 一部導線を整理し、手動ボタン依存を減らした運用に再設計。
### ユーザーにとっての効果
- 追跡中の情報が途切れにくくなり、移動中でも状況把握しやすくなります。
- 通知表示が実運用寄りになり、今どこを走っているかを直感的に確認できます。
## 4. FeliCa機能の拡張
### 実装/更新された機能
- NFC読み取り基盤を安定化し、モジュール実装を補強。
- FeliCa履歴表示を拡張カード種別、残高計算、駅名補完、コピー操作など
- 可用性チェックと画面導線を改善し、失敗時の扱いも見直し。
### ユーザーにとっての効果
- 読み取り結果の活用範囲が広がり、履歴確認が実用的になります。
- 読み取り失敗時のストレスが減り、再試行しやすくなります。
## 5. ウィジェット体験の強化
### 実装/更新された機能
- Shortcut / Delay Info / Felica Balance系ウィジェットを追加・改善。
- タイル表示、背景、時刻表示などの見た目と可読性を調整。
- ウィジェット経由の情報取得フローを改善。
### ユーザーにとっての効果
- アプリを開かずに必要情報へ素早くアクセスできます。
- ホーム画面での情報確認が見やすく、操作回数を減らせます。
## 6. UI/UX・操作性の改善
### 実装/更新された機能
- 駅時刻表/ダイヤ表示に自動スクロールや測定ロジック改善を適用。
- キーボード回避ロジックを共通化し、入力中のレイアウト崩れを抑制。
- カルーセルのソート/グリッド表示、観光スポット表示などを拡張。
- ダークモード周辺や各種表示バグを修正。
### ユーザーにとっての効果
- 検索や閲覧が引っかかりにくくなり、操作テンポが向上します。
- 情報密度が高い画面でも見やすく、目的の情報へ到達しやすくなります。
## 7. ビルド基盤・内部品質の改善
### 実装/更新された機能
- Expo SDKを段階的に更新し、依存関係を整理。
- New Architecture移行準備を進め、将来対応の下地を整備。
- Autolinking/EAS関連の設定を見直し、ビルド安定性を改善。
### ユーザーにとっての効果
- 直接見えない部分ですが、クラッシュ/不整合の予防と将来アップデートの安定性向上につながります。
---
## まとめ
7.0beta は、単発の機能追加よりも「連携データの信頼性」「追跡通知の実用性」「日常操作の快適性」を底上げしたリリースです。
運用面では、外部データ連携と通知の改善が大きな柱で、FeliCa・ウィジェット・UI改善がその体験を支える構成になっています。
## 参照
- コミット差分精査版: `docs/changelog-6.2.1.1-to-7.0beta.md`

View File

@@ -0,0 +1,157 @@
# 6.2.1.1 -> 7.0beta 更新ログ(コミット差分精査版)
## 対象範囲
- From: `50822c6c7464c7071a828d510293b4aae9c4e86c`
- To: `baacfd5855eee7f74ce1d770a9414f4e15f09c10`
- 集計メモ:
- レンジ内コミットmerge含む: 166
- 非mergeコミット: 144
- 件名重複を除いた実質トピック: 117
## サマリー
6.2.1.1 -> 7.0beta では、列車追跡通知/Live Activity 系を中心に、情報ソース連携Unyohub/elesite、FeliCa機能、ウィジェット、UI/操作性、ビルド基盤の改善が並行して進められました。
特に「外部データ連携の強化」「通知とバックグラウンド更新の改善」「設定/画面遷移の安定化」がユーザー体験に直結する主な更新です。
---
## 1. 鉄道運用HubUnyohub連携の追加・強化
- 連携データの表示品質を改善:
- 日付整形、stale判定鮮度チェックを追加し、古い情報の見分けをしやすく改善。
- 情報ソース表示の改善:
- 列車情報ソース表示まわりで、URLオープンや表示循環などの挙動を安定化。
- 外部ソース統合の実装整理:
- Header/TrainDataSources 側で統合表示ロジックを強化。
主なコミット:
- `814de31` feat: add date formatting and stale check for Unyohub entries in TrainDataSources
- `7d7b184` feat: add last reported timestamp and update TrainDataSources for elesite integration
- `30d1111` feat: enhance HeaderText component with elesite integration and improve layout handling
- `cc15e6a` feat: update elesite integration to prioritize non-empty formation units and improve sorting logic
- `6665076` feat: add elesite integration and configuration settings
## 2. ON/OFF・情報ソース管理・アクセス経路の整備
- 情報ソース管理の共通化:
- ソース管理の統合、表示モード整理により設定と実表示の一貫性を改善。
- 設定画面/導線の整理:
- ウィジェット設定導線をプラットフォーム条件に合わせて整理。
- 画面遷移の安定化:
- 通知タップ時の遷移やDeep Link経路を調整し、意図しない遷移を抑制。
主なコミット:
- `ab92cc7` feat: unify station source management and enhance carousel UI modes
- `18f11c8` WidgetSettingsコンポーネントを削除し、SettingTopPageのウィジェット設定リンクをAndroidプラットフォームに限定
- `ecc9ee3` feat: update navigation handling and widget click actions for improved user experience
- `f34d061` fix: アプリ起動時の意図しない自動画面遷移バグを修正
## 3. 列車追跡通知・Live Activity の実装と再設計
- 初期導入:
- 列車追跡/駅固定向けLive Activityと通知更新を実装。
- 強化:
- 自動更新、進捗表示、次駅計算、バックグラウンド更新継続を改善。
- 再整理:
- `LiveActivityButton` 方式から自動開始/更新中心へ再設計し、UI導線を簡素化。
主なコミット:
- `75c07f0` feat: add Live Activities support for train tracking and station locking
- `b8372e5` feat: add hooks for managing Live Activities for station locking and train following
- `7f2480b` feat: add Live Activity support for station locking in StationDiagramView
- `c5d4dc3` feat: add automatic Live Activity updates for train position and station lock in TrainDataView and StationDiagramView
- `fbfb83f` feat: add live activity notifications for train tracking and station locking
- `86123ec` feat: implement train tracking notifications with background polling and update UI components
- `86a4428` feat: 通知進捗バー再設計 - 全駅対応の進捗表示
- `13f2c4d` feat: 駅固定モード バックグラウンド更新 + 通知書式改善
- `2cd5142` fix: バックグラウンドでのデータ取得を継続し、列車追跡の終了条件をフォアグラウンド依存から改善
- `dc3d250` fix: 次駅表示をDirection非依存に修正JS/Kotlin両方
- `baacfd5` refactor: remove LiveActivityButton from EachTrainInfoCore component
- `0bcb03f` refactor: remove live notification functionality from FixedStation and FixedTrain components
## 4. FeliCa 機能の拡張
- NFC読み取りの基盤強化:
- ExpoFelicaReader モジュールの実装補強、診断改善、設定/サービスコード修正。
- 履歴機能の拡張:
- FeliCa履歴ページ、カード種別表示、残高計算、駅名補完、コピー操作などを追加。
- 可用性チェック/ウィジェット連携:
- 利用可能判定、Quick Access系ウィジェットとの連動を改善。
主なコミット:
- `41cc700` Implement NFC scanning functionality in ExpoFelicaReader module
- `9f6d86b` Fix ExpoFelicaReader module: restore full definition, update diagnostics
- `ec4db3d` feat: implement FeliCa history page and update navigation
- `6a66429` feat: add FeliCa transaction history retrieval and update data structures
- `be88a46` feat(felica): update build/version and enhance history page
- `616846e` feat(felica): enhance history row with balance calculation and long press copy
- `8bc7266` feat(felica): add station name lookup from FeliCa history
- `45feeec` feat: FeliCa対応の可用性チェック機能を追加
## 5. Android/iOS ウィジェット強化
- 追加/改善:
- Shortcut、Delay Info、Felica Balance 系ウィジェットを追加・改善。
- 表示改善:
- タイル表示、背景、スキャン時刻、情報取得導線などを改善。
主なコミット:
- `06650d0` feat(widget): add Shortcut, Delay Info, and Felica Balance widgets
- `cee238d` ShortcutWidgetのタイル表示を改善し、遅延情報と運行情報取得を追加
- `2d96bdc` fix: Felicaウィジェットにスキャンタイムスタンプを追加
- `4b518b8` / `f4b86f4` fix: ウィジェットUI改善
## 6. UI/UX・操作性改善
- 駅時刻表/ダイヤ表示:
- 次時間帯への自動スクロール、レイアウト測定対象の見直し、キーボード回避の共通化。
- カルーセル/トップメニュー:
- ソート・グリッド表示追加、動作改善、観光スポット表示の拡張。
- 細かな品質改善:
- ダークモード対応、アイコン共有/表示バグ修正、URL処理の簡素化。
主なコミット:
- `dad462f` ListViewとExGridSimpleViewに次時間帯への自動スクロールを追加
- `a2912d7` キーボード回避ロジックをhookに共通化
- `2142d90` カルーセルのソート/グリッド表示を追加
- `5202f35` feat: 与島(観光スポット)をトップメニューに追加
- `066317b` feat: add SpotSign component for tourist attractions
- `59653bb` fix: ダークモードに対応し背景色を動的変更
## 7. ビルド基盤・依存関係・開発運用
- Expo SDK更新:
- SDK 53 -> 54 -> 55 の段階的アップグレードを実施。
- New Architecture 準備:
- app/babel/metro/package 周辺を調整し移行準備を進行。
- Autolinking/EAS対策:
- iOS除外設定修正、Pod/debug補助、依存追加などを実施。
主なコミット:
- `10df37d` feat: Expo SDK 52->53 upgrade + full dark mode support
- `b7a09ed` feat: Expo SDK 53 -> 54 upgrade (RN 0.81.5)
- `bf4a591` upgrade: Expo SDK 54 -> 55
- `cf611c6` feat: 新アーキテクチャ移行準備と依存更新
- `98c7112` fix: .gitignore修正modules/**/ios再包含
- `72e7d63` add expo-felica-reader dependency for EAS autolinking
---
## 変更リスクと影響範囲(運用メモ)
- 影響が大きい領域:
- 通知/Live Activity系バックグラウンド処理、端末権限、更新頻度
- FeliCaネイティブモジュールAndroid/iOS双方
- Expo SDK更新に伴う依存差分
- 検証優先度:
- 通知権限拒否時の挙動
- バックグラウンド復帰時の同期
- FeliCa読み取り失敗時のリカバリ
- ウィジェットタップからの画面遷移
## 付録A: コミット抽出方針
- 本書は対象レンジの非mergeコミットをベースに、人が機能差分を理解しやすいようにトピック化。
- 同一趣旨の重複コミット(マージ由来)は説明本文では統合。
- 監査時はコミットハッシュを直接参照して追跡可能。
## 付録B: 代表コミット一覧(抜粋)
- `baacfd5` refactor: remove LiveActivityButton from EachTrainInfoCore component
- `814de31` feat: add date formatting and stale check for Unyohub entries in TrainDataSources
- `86123ec` feat: implement train tracking notifications with background polling and update UI components
- `75c07f0` feat: add Live Activities support for train tracking and station locking
- `45feeec` feat: FeliCa対応の可用性チェック機能を追加
- `06650d0` feat(widget): add Shortcut, Delay Info, and Felica Balance widgets
- `2142d90` カルーセルにソート機能を追加し、グリッド表示を実装
- `bf4a591` upgrade: Expo SDK 54 -> 55

View File

@@ -5,7 +5,7 @@ export const findReversalPoints = (array, stopStationIDList,isExcludeStopStation
const stopStationIDListFiltered = isExcludeStopStation ? stopStationIDList.filter((d,index,array)=>d[0] !== array[index == 0 ? index : index -1][0]):stopStationIDList;
if (!stopStationIDListFiltered) return [];
// arrayが二次元配列だったら早期リターン
if (!array instanceof Array) return [];
if (!(array instanceof Array)) return [];
if (!array) return [];
if (array[0] instanceof Array) return [];

View File

@@ -13,7 +13,7 @@ export const initIcon = (
return ({ focused, color, size }) => (
<>
{!!tabBarBadge && <Badge tabBarBadge={tabBarBadge} isInfo={isInfo} />}
<IconComponent name={name} size={30} color={color} />
<IconComponent name={name as any} size={30} color={color} />
</>
);
};

View File

@@ -11,7 +11,7 @@ type State = "RUNNING" | "STOPPED";
type Fn = () => void;
export const useInterval = (fn: Fn, interval: number, autostart = true, keepAliveInBackground = false) => {
const onUpdateRef = useRef<Fn>();
const onUpdateRef = useRef<Fn>(undefined);
const [state, setState] = useState("RUNNING");
// ユーザー操作によるSTOPAppStateによる一時停止と区別する
const userStoppedRef = useRef(!autostart);

View File

@@ -35,8 +35,6 @@ const initialState = {
getPosition: ((e) => {}) as (e: trainDataType) => string[] | undefined,
fixedPositionSize: 80,
setFixedPositionSize: (e) => {},
liveNotificationActive: false,
setLiveNotificationActive: (e) => {},
};
type initialStateType = {
@@ -61,8 +59,6 @@ type initialStateType = {
getPosition: (e: trainDataType) => string[] | undefined;
fixedPositionSize: number;
setFixedPositionSize: (e: number) => void;
liveNotificationActive: boolean;
setLiveNotificationActive: (e: boolean) => void;
};
const CurrentTrainContext = createContext<initialStateType>(initialState);
@@ -93,7 +89,6 @@ export const CurrentTrainProvider: FC<props> = ({ children }) => {
value: null,
});
const [fixedPositionSize, setFixedPositionSize] = useState(80);
const [liveNotificationActive, setLiveNotificationActive] = useState(false);
const [_, setIntervalState] = useInterval(() => {
if (!webview.current) return;
if (fixedPosition.type == "station") {
@@ -352,8 +347,6 @@ export const CurrentTrainProvider: FC<props> = ({ children }) => {
nearestStationID,
fixedPositionSize,
setFixedPositionSize,
liveNotificationActive,
setLiveNotificationActive,
}}
>
{children}

View File

@@ -3,6 +3,7 @@ import { AS } from "../storageControl";
import { STORAGE_KEYS } from "@/constants";
import { API_ENDPOINTS } from "@/constants";
import type { ElesiteResponse, ElesiteData } from "@/types/unyohub";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
type ElesiteHook = {
/** えれサイト使用設定 */
@@ -18,13 +19,15 @@ type ElesiteHook = {
};
export const useElesite = (): ElesiteHook => {
const { dataSourcePermission, updatePermission } = useTrainMenu();
const canUseElesite = updatePermission || dataSourcePermission.elesite;
const [useElesite, setUseElesiteState] = useState(false);
const [elesiteData, setElesiteData] = useState<ElesiteResponse>([]);
// 初期読み込み
useEffect(() => {
AS.getItem(STORAGE_KEYS.USE_ELESITE).then((value) => {
setUseElesiteState(value === true || value === "true");
setUseElesiteState((value === true || value === "true") && canUseElesite);
});
AS.getItem(STORAGE_KEYS.ELESITE_DATA).then((value) => {
@@ -36,7 +39,14 @@ export const useElesite = (): ElesiteHook => {
}
}
});
}, []);
}, [canUseElesite]);
// 権限がない場合は設定を強制OFF
useEffect(() => {
if (canUseElesite) return;
setUseElesiteState(false);
AS.setItem(STORAGE_KEYS.USE_ELESITE, "false");
}, [canUseElesite]);
// データ更新処理
useEffect(() => {
@@ -104,8 +114,9 @@ export const useElesite = (): ElesiteHook => {
// 設定を更新
const setUseElesite = (value: boolean) => {
setUseElesiteState(value);
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
const next = value && canUseElesite;
setUseElesiteState(next);
AS.setItem(STORAGE_KEYS.USE_ELESITE, next.toString());
};
return {

View File

@@ -27,6 +27,8 @@ Notifications.setNotificationHandler({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true,
shouldShowList: true,
}),
});

View File

@@ -17,7 +17,7 @@ type initialStateType = {
const initialState = {
originalStationList: [[]],
setOriginalStationList: () => {},
getStationData: () => {},
getStationData: () => [],
stationList: [],
};

View File

@@ -40,7 +40,10 @@ const initialState = {
updatePermission: false,
setUpdatePermission: (e) => {},
/** 各情報ソースの利用権限 */
dataSourcePermission: { unyohub: false } as { unyohub: boolean },
dataSourcePermission: { unyohub: false, elesite: false } as {
unyohub: boolean;
elesite: boolean;
},
injectJavascript: "",
};
@@ -66,11 +69,14 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
//更新権限所有確認・情報ソース別利用権限(将来ロールが増えたらここに足す)
const [updatePermission, setUpdatePermission] = useState(false);
const [dataSourcePermission, setDataSourcePermission] = useState<{ unyohub: boolean }>({ unyohub: false });
const [dataSourcePermission, setDataSourcePermission] = useState<{
unyohub: boolean;
elesite: boolean;
}>({ unyohub: false, elesite: false });
useEffect(() => {
if (!expoPushToken) return;
fetch(
`https://jr-shikoku-backend-api-v1.haruk.in/check-permission?user_id=${expoPushToken}`
`https://jr-shikoku-backend-api-v1.haruk.in/check-permission?user_id=${expoPushToken}`,
)
.then((res) => res.json())
.then((res) => {
@@ -78,6 +84,7 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
setUpdatePermission(role === "administrator");
setDataSourcePermission({
unyohub: role === "administrator" || role === "unyoHubEditor",
elesite: role === "administrator" || role === "eleSiteEditor",
});
})
.catch(() => {});
@@ -95,7 +102,7 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
//GUIデザインベース
const [uiSetting, setUiSetting] = useState("tokyo");
// 鉄道運用Hub使用設定
const [useUnyohubSetting, setUseUnyohubSetting] = useState("false");
@@ -115,19 +122,49 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
useEffect(() => {
//列車アイコンスイッチ
ASCore({ k: STORAGE_KEYS.ICON_SWITCH, s: setIconSetting, d: "true", u: true });
ASCore({
k: STORAGE_KEYS.ICON_SWITCH,
s: setIconSetting,
d: "true",
u: true,
});
//地図スイッチ
ASCore({ k: STORAGE_KEYS.MAP_SWITCH, s: setMapSwitch, d: "true", u: true });
//駅メニュースイッチ
ASCore({ k: STORAGE_KEYS.STATION_SWITCH, s: setStationMenu, d: "true", u: true });
ASCore({
k: STORAGE_KEYS.STATION_SWITCH,
s: setStationMenu,
d: "true",
u: true,
});
//列車メニュースイッチ
ASCore({ k: STORAGE_KEYS.TRAIN_SWITCH, s: setTrainMenu, d: "true", u: true });
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 });
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 });
ASCore({
k: STORAGE_KEYS.USE_UNYOHUB,
s: setUseUnyohubSetting,
d: "false",
u: true,
});
//えれサイトスイッチ
ASCore({ k: STORAGE_KEYS.USE_ELESITE, s: setUseEleSiteSetting, d: "false", u: true });
ASCore({
k: STORAGE_KEYS.USE_ELESITE,
s: setUseEleSiteSetting,
d: "false",
u: true,
});
}, []);
return (