Compare commits
38 Commits
feature/fi
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
834ad2bd41 | ||
|
|
83ca18f2c7 | ||
|
|
baacfd5855 | ||
|
|
066317bbc8 | ||
|
|
0bcb03f833 | ||
|
|
9037d21237 | ||
|
|
4789543573 | ||
|
|
5345dca95f | ||
|
|
b1a8a4c98f | ||
|
|
50fd2ece64 | ||
|
|
f972d2b719 | ||
|
|
deb24caaa2 | ||
|
|
5515f42415 | ||
|
|
fbfb83fa34 | ||
|
|
8eb49f57d6 | ||
|
|
c5d4dc3b65 | ||
|
|
7f2480bc01 | ||
|
|
b8372e5087 | ||
|
|
75c07f013d | ||
|
|
46bfea4e13 | ||
|
|
5a2dc8c6a8 | ||
|
|
06650d014a | ||
|
|
45feeece58 | ||
|
|
385c2d8b86 | ||
|
|
41cc70086a | ||
|
|
98c71127aa | ||
|
|
d049d3b07d | ||
|
|
8484f15092 | ||
|
|
1afa5e4377 | ||
|
|
9f6d86b8b6 | ||
|
|
72e7d63bd7 | ||
|
|
b243439e78 | ||
|
|
65f3b2a877 | ||
|
|
925d162f26 | ||
|
|
731fe504c6 | ||
|
|
942ec395f1 | ||
|
|
306cf6882e | ||
|
|
468bb4633a |
@@ -107,7 +107,7 @@ export function MenuPage() {
|
|||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
id={null}
|
id={null}
|
||||||
screenOptions={{ contentStyle: { backgroundColor: bgColor } }}
|
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
|
||||||
>
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="menu"
|
name="menu"
|
||||||
|
|||||||
4
Top.tsx
4
Top.tsx
@@ -19,7 +19,7 @@ import { StationDiagramView } from "@/components/StationDiagram/StationDiagramVi
|
|||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator();
|
||||||
export const Top = () => {
|
export const Top = () => {
|
||||||
const { webview } = useCurrentTrain();
|
const { webview } = useCurrentTrain();
|
||||||
const { navigate, addListener, isFocused } = useNavigation();
|
const { navigate, addListener, isFocused } = useNavigation<any>();
|
||||||
const isDark = useColorScheme() === "dark";
|
const isDark = useColorScheme() === "dark";
|
||||||
const bgColor = isDark ? "#1c1c1e" : "#ffffff";
|
const bgColor = isDark ? "#1c1c1e" : "#ffffff";
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export const Top = () => {
|
|||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
id={null}
|
id={null}
|
||||||
screenOptions={{ contentStyle: { backgroundColor: bgColor } }}
|
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
|
||||||
>
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Apps"
|
name="Apps"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ export const EachTrainInfoCore = ({
|
|||||||
} else {
|
} else {
|
||||||
SheetManager.hide("EachTrainInfo").then(() => {
|
SheetManager.hide("EachTrainInfo").then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// @ts-expect-error - SheetManager payload type is too restrictive
|
|
||||||
SheetManager.show("EachTrainInfo", { payload });
|
SheetManager.show("EachTrainInfo", { payload });
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const useTrainPosition = (
|
|||||||
const trainPosData = getCurrentStationData(trainNum);
|
const trainPosData = getCurrentStationData(trainNum);
|
||||||
if (trainPosData) {
|
if (trainPosData) {
|
||||||
logger.debug('Train position data:', trainPosData);
|
logger.debug('Train position data:', trainPosData);
|
||||||
setCurrentTrainData(trainPosData);
|
setCurrentTrainData(trainPosData as TrainData);
|
||||||
}
|
}
|
||||||
}, [currentTrain, trainNum, getCurrentStationData]);
|
}, [currentTrain, trainNum, getCurrentStationData]);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const JRSTraInfo = () => {
|
|||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const timeData = dayjs(getTime).format("HH:mm");
|
const timeData = dayjs(getTime).format("HH:mm");
|
||||||
const actionSheetRef = useRef(null);
|
const actionSheetRef = useRef(null);
|
||||||
const scrollHandlers = useScrollHandlers("scrollview-1", actionSheetRef);
|
const scrollHandlers = useScrollHandlers();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const viewShot = useRef(null);
|
const viewShot = useRef(null);
|
||||||
|
|
||||||
@@ -59,7 +59,8 @@ export const JRSTraInfo = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: fixed.primary,
|
backgroundColor: fixed.primary,
|
||||||
borderTopRadius: 5,
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
borderColor: colors.border,
|
borderColor: colors.border,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
}}
|
}}
|
||||||
@@ -104,7 +105,7 @@ export const JRSTraInfo = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<ScrollView {...scrollHandlers}>
|
<ScrollView {...scrollHandlers as any}>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
padding: 10,
|
padding: 10,
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export const Social = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: fixed.primary,
|
backgroundColor: fixed.primary,
|
||||||
borderTopRadius: 5,
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
borderColor: "dark",
|
borderColor: "dark",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
@@ -65,6 +66,7 @@ export const Social = () => {
|
|||||||
<ListItem
|
<ListItem
|
||||||
bottomDivider
|
bottomDivider
|
||||||
onPress={() => Linking.openURL("tel:0570-00-4592")}
|
onPress={() => Linking.openURL("tel:0570-00-4592")}
|
||||||
|
// @ts-ignore: TouchableScale props passed through Component
|
||||||
friction={90}
|
friction={90}
|
||||||
tension={100}
|
tension={100}
|
||||||
activeScale={0.95}
|
activeScale={0.95}
|
||||||
@@ -80,7 +82,8 @@ export const Social = () => {
|
|||||||
0570-00-4592(8:00〜20:00 年中無休)
|
0570-00-4592(8:00〜20:00 年中無休)
|
||||||
</ListItem.Subtitle>
|
</ListItem.Subtitle>
|
||||||
</ListItem.Content>
|
</ListItem.Content>
|
||||||
<ListItem.Chevron color={fixed.textOnPrimary} />
|
{/* @ts-ignore: ListItem.Chevron doesn't expose color prop */}
|
||||||
|
<ListItem.Chevron color={"white"} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{
|
style={{
|
||||||
@@ -150,6 +153,7 @@ export const Social = () => {
|
|||||||
bottomDivider
|
bottomDivider
|
||||||
onPress={() => Linking.openURL(d.url)}
|
onPress={() => Linking.openURL(d.url)}
|
||||||
key={d.url}
|
key={d.url}
|
||||||
|
// @ts-ignore: TouchableScale props passed through Component
|
||||||
friction={90} //
|
friction={90} //
|
||||||
tension={100} // These props are passed to the parent component (here TouchableScale)
|
tension={100} // These props are passed to the parent component (here TouchableScale)
|
||||||
activeScale={0.95} //
|
activeScale={0.95} //
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export const SpecialTrainInfo: FC<props> = ({ payload }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: fixed.primary,
|
backgroundColor: fixed.primary,
|
||||||
borderTopRadius: 5,
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
borderColor: "dark",
|
borderColor: "dark",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const StationDeteilView = (props) => {
|
|||||||
const { currentStation, navigate, onExit, goTo, useShow } = props.payload;
|
const { currentStation, navigate, onExit, goTo, useShow } = props.payload;
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const { busAndTrainData } = useBusAndTrainData();
|
const { busAndTrainData } = useBusAndTrainData();
|
||||||
const [trainBus, setTrainBus] = useState();
|
const [trainBus, setTrainBus] = useState<any>(undefined);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,7 +38,7 @@ export const StationDeteilView = (props) => {
|
|||||||
(d) => d.name === currentStation[0].Station_JP
|
(d) => d.name === currentStation[0].Station_JP
|
||||||
);
|
);
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
setTrainBus();
|
setTrainBus(undefined);
|
||||||
}
|
}
|
||||||
setTrainBus(data[0]);
|
setTrainBus(data[0]);
|
||||||
}, [currentStation, busAndTrainData]);
|
}, [currentStation, busAndTrainData]);
|
||||||
@@ -72,7 +72,8 @@ export const StationDeteilView = (props) => {
|
|||||||
key={currentStation}
|
key={currentStation}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.sheetBackground,
|
backgroundColor: colors.sheetBackground,
|
||||||
borderTopRadius: 5,
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
borderColor: "dark",
|
borderColor: "dark",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ export const TrainIconUpdate = () => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: fixed.primary,
|
backgroundColor: fixed.primary,
|
||||||
borderTopRadius: 5,
|
borderTopLeftRadius: 5,
|
||||||
|
borderTopRightRadius: 5,
|
||||||
borderColor: "dark",
|
borderColor: "dark",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
14
components/ActionSheetComponents/sheets.d.ts
vendored
Normal file
14
components/ActionSheetComponents/sheets.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { registerSheet } from "react-native-actions-sheet";
|
import { registerSheet, SheetDefinition } from "react-native-actions-sheet";
|
||||||
import { EachTrainInfo } from "./EachTrainInfo";
|
import { EachTrainInfo } from "./EachTrainInfo";
|
||||||
import { JRSTraInfo } from "./JRSTraInfo";
|
import { JRSTraInfo } from "./JRSTraInfo";
|
||||||
import { StationDeteilView } from "./StationDeteilView";
|
import { StationDeteilView } from "./StationDeteilView";
|
||||||
@@ -18,3 +18,16 @@ registerSheet("Social", Social);
|
|||||||
registerSheet("TrainDataSources", TrainDataSources);
|
registerSheet("TrainDataSources", TrainDataSources);
|
||||||
|
|
||||||
export {};
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { getStringConfig } from "@/lib/getStringConfig";
|
|||||||
|
|
||||||
export const AllTrainDiagramView: FC = () => {
|
export const AllTrainDiagramView: FC = () => {
|
||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const { goBack, navigate } = useNavigation();
|
const { goBack, navigate } = useNavigation<any>();
|
||||||
const tabBarHeight = useBottomTabBarHeight();
|
const tabBarHeight = useBottomTabBarHeight();
|
||||||
const {
|
const {
|
||||||
keyList,
|
keyList,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function InfoWidget({ time, text }) {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: "#0099CC",
|
backgroundColor: "#0099CC",
|
||||||
width: "100%",
|
width: "100%" as any,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
@@ -78,7 +78,7 @@ export function InfoWidget({ time, text }) {
|
|||||||
width: "match_parent",
|
width: "match_parent",
|
||||||
height: "match_parent",
|
height: "match_parent",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
}}
|
} as any}
|
||||||
>
|
>
|
||||||
{text ? (
|
{text ? (
|
||||||
<TextWidget
|
<TextWidget
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ function GridTile({
|
|||||||
{sub !== undefined ? (
|
{sub !== undefined ? (
|
||||||
<TextWidget
|
<TextWidget
|
||||||
text={sub}
|
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 }} />
|
<TextWidget text="" style={{ fontSize: 1 }} />
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function TraInfoEXWidget({ time, delayString }) {
|
|||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: "#0099CC",
|
backgroundColor: "#0099CC",
|
||||||
width: "100%",
|
width: "100%" as any,
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
@@ -77,7 +77,7 @@ export function TraInfoEXWidget({ time, delayString }) {
|
|||||||
width: "match_parent",
|
width: "match_parent",
|
||||||
height: "match_parent",
|
height: "match_parent",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
}}
|
} as any}
|
||||||
>
|
>
|
||||||
{delayString ? (
|
{delayString ? (
|
||||||
delayString.map((d) => {
|
delayString.map((d) => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { FixedPositionBox } from "./Apps/FixedPositionBox";
|
|||||||
export default function Apps() {
|
export default function Apps() {
|
||||||
const { webview, fixedPosition, setFixedPosition } = useCurrentTrain();
|
const { webview, fixedPosition, setFixedPosition } = useCurrentTrain();
|
||||||
const { height, width } = useWindowDimensions();
|
const { height, width } = useWindowDimensions();
|
||||||
const { navigate } = useNavigation();
|
const { navigate } = useNavigation<any>();
|
||||||
const { isLandscape } = useDeviceOrientationChange();
|
const { isLandscape } = useDeviceOrientationChange();
|
||||||
const { top } = useSafeAreaInsets();
|
const { top } = useSafeAreaInsets();
|
||||||
const handleLayout = () => {};
|
const handleLayout = () => {};
|
||||||
@@ -69,13 +69,11 @@ export default function Apps() {
|
|||||||
navigate,
|
navigate,
|
||||||
goTo: "Apps",
|
goTo: "Apps",
|
||||||
useShow: () => {
|
useShow: () => {
|
||||||
// @ts-expect-error - SheetManager payload type is too restrictive
|
|
||||||
SheetManager.show("StationDetailView", { payload });
|
SheetManager.show("StationDetailView", { payload });
|
||||||
},
|
},
|
||||||
onExit: () => SheetManager.hide("StationDetailView"),
|
onExit: () => SheetManager.hide("StationDetailView"),
|
||||||
};
|
};
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// @ts-expect-error - SheetManager payload type is too restrictive
|
|
||||||
SheetManager.show("StationDetailView", { payload });
|
SheetManager.show("StationDetailView", { payload });
|
||||||
}, 50);
|
}, 50);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
|
|||||||
setFixedPosition,
|
setFixedPosition,
|
||||||
fixedPositionSize,
|
fixedPositionSize,
|
||||||
setFixedPositionSize,
|
setFixedPositionSize,
|
||||||
liveNotificationActive,
|
|
||||||
setLiveNotificationActive,
|
|
||||||
} = useCurrentTrain();
|
} = useCurrentTrain();
|
||||||
const { getStationDataFromId } = useStationList();
|
const { getStationDataFromId } = useStationList();
|
||||||
const { stationList } = useStationList();
|
const { stationList } = useStationList();
|
||||||
@@ -117,6 +115,13 @@ export const FixedStation: FC<props> = ({ stationID }) => {
|
|||||||
const [selectedTrain, setSelectedTrain] = useState<eachTrainDiagramType[]>(
|
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(() => {
|
useEffect(() => {
|
||||||
if (!trainTimeAndNumber) return () => {};
|
if (!trainTimeAndNumber) return () => {};
|
||||||
if (!currentTrain) return () => {};
|
if (!currentTrain) return () => {};
|
||||||
@@ -128,12 +133,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
|
|||||||
setSelectedTrain(data);
|
setSelectedTrain(data);
|
||||||
}, [trainTimeAndNumber, currentTrain, tick /*liveActivity periodic refresh*/]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
liveNotifyIdRef.current = liveNotifyId;
|
liveNotifyIdRef.current = liveNotifyId;
|
||||||
}, [liveNotifyId]);
|
}, [liveNotifyId]);
|
||||||
@@ -149,7 +148,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (liveNotifyIdRef.current) {
|
if (liveNotifyIdRef.current) {
|
||||||
endStationLockActivity(liveNotifyIdRef.current).catch(() => {});
|
endStationLockActivity(liveNotifyIdRef.current).catch(() => {});
|
||||||
setLiveNotificationActive(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -225,7 +223,6 @@ export const FixedStation: FC<props> = ({ stationID }) => {
|
|||||||
trains,
|
trains,
|
||||||
});
|
});
|
||||||
setLiveNotifyId(id);
|
setLiveNotifyId(id);
|
||||||
setLiveNotificationActive(true);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[LiveNotify] start error:', e);
|
console.warn('[LiveNotify] start error:', e);
|
||||||
hasStartedRef.current = false;
|
hasStartedRef.current = false;
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
|
|||||||
getPosition,
|
getPosition,
|
||||||
fixedPositionSize,
|
fixedPositionSize,
|
||||||
setFixedPositionSize,
|
setFixedPositionSize,
|
||||||
liveNotificationActive,
|
|
||||||
setLiveNotificationActive,
|
|
||||||
} = useCurrentTrain();
|
} = useCurrentTrain();
|
||||||
|
|
||||||
const { mapSwitch } = useTrainMenu();
|
const { mapSwitch } = useTrainMenu();
|
||||||
@@ -420,14 +418,13 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (liveNotifyIdRef.current) {
|
if (liveNotifyIdRef.current) {
|
||||||
endTrainFollowActivity(liveNotifyIdRef.current).catch(() => {});
|
endTrainFollowActivity(liveNotifyIdRef.current).catch(() => {});
|
||||||
setLiveNotificationActive(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!liveNotifyId || !train) return;
|
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 delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
|
||||||
const positionStatus =
|
const positionStatus =
|
||||||
nextStationData[0]?.Station_JP === train.Pos ? "ただいま" : "次は";
|
nextStationData[0]?.Station_JP === train.Pos ? "ただいま" : "次は";
|
||||||
@@ -463,7 +460,7 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
|
|||||||
);
|
);
|
||||||
if (granted !== PermissionsAndroid.RESULTS.GRANTED) return;
|
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 delayStatus = delayNum > 0 ? `${delayNum}分遅れ` : "定刻";
|
||||||
const positionStatus =
|
const positionStatus =
|
||||||
nextStationData[0]?.Station_JP === train?.Pos ? "ただいま" : "次は";
|
nextStationData[0]?.Station_JP === train?.Pos ? "ただいま" : "次は";
|
||||||
@@ -488,7 +485,6 @@ export const FixedTrain: FC<props> = ({ trainID }) => {
|
|||||||
currentStationIndex,
|
currentStationIndex,
|
||||||
});
|
});
|
||||||
setLiveNotifyId(id);
|
setLiveNotifyId(id);
|
||||||
setLiveNotificationActive(true);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[LiveNotify] start error:', e);
|
console.warn('[LiveNotify] start error:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useTrainMenu } from "../../stateBox/useTrainMenu";
|
|||||||
import { useStationList } from "../../stateBox/useStationList";
|
import { useStationList } from "../../stateBox/useStationList";
|
||||||
export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
|
export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
|
||||||
const { webview, currentTrain } = useCurrentTrain();
|
const { webview, currentTrain } = useCurrentTrain();
|
||||||
const { navigate } = useNavigation();
|
const { navigate } = useNavigation<any>();
|
||||||
const { favoriteStation } = useFavoriteStation();
|
const { favoriteStation } = useFavoriteStation();
|
||||||
const { isLandscape } = useDeviceOrientationChange();
|
const { isLandscape } = useDeviceOrientationChange();
|
||||||
const isDark = useColorScheme() === "dark";
|
const isDark = useColorScheme() === "dark";
|
||||||
@@ -149,7 +149,6 @@ export const AppsWebView = ({ openStationACFromEachTrainInfo }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<WebView
|
<WebView
|
||||||
useWebKit
|
|
||||||
ref={webview}
|
ref={webview}
|
||||||
source={{ uri: "https://train.jr-shikoku.co.jp/sp.html" }}
|
source={{ uri: "https://train.jr-shikoku.co.jp/sp.html" }}
|
||||||
originWhitelist={[
|
originWhitelist={[
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const FavoriteList: FC = () => {
|
|||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const { favoriteStation } = useFavoriteStation();
|
const { favoriteStation } = useFavoriteStation();
|
||||||
const { webview } = useCurrentTrain();
|
const { webview } = useCurrentTrain();
|
||||||
const { navigate, addListener, goBack, canGoBack } = useNavigation();
|
const { navigate, addListener, goBack, canGoBack } = useNavigation<any>();
|
||||||
const { mapsStationData: stationData } = useTrainMenu();
|
const { mapsStationData: stationData } = useTrainMenu();
|
||||||
const { getInjectJavascriptAddress } = useStationList();
|
const { getInjectJavascriptAddress } = useStationList();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Sign from "@/components/駅名表/Sign";
|
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 React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { AS } from "@/storageControl";
|
import { AS } from "@/storageControl";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const CarouselTypeChanger = ({
|
|||||||
setMapMode: React.Dispatch<React.SetStateAction<boolean>>;
|
setMapMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
const { fixedPosition, setFixedPosition } = useCurrentTrain();
|
const { fixedPosition, setFixedPosition } = useCurrentTrain();
|
||||||
const { navigate } = useNavigation();
|
const { navigate } = useNavigation<any>();
|
||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const isGpsFollowing = fixedPosition?.type === "nearestStation";
|
const isGpsFollowing = fixedPosition?.type === "nearestStation";
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ type DataSourceAccordionCardProps = {
|
|||||||
linkLabel: string;
|
linkLabel: string;
|
||||||
/** フッターリンク URL */
|
/** フッターリンク URL */
|
||||||
linkUrl: string;
|
linkUrl: string;
|
||||||
|
/** 詳細ラベル */
|
||||||
|
detailLabel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||||
@@ -156,9 +158,9 @@ const ELESITE_FEATURES: Feature[] = [
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
export const DataSourceSettings = () => {
|
export const DataSourceSettings = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { updatePermission, dataSourcePermission } = useTrainMenu();
|
const { dataSourcePermission, updatePermission } = useTrainMenu();
|
||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const canAccess = updatePermission || Object.values(dataSourcePermission).some(Boolean);
|
const canUseElesite = updatePermission || dataSourcePermission.elesite;
|
||||||
const [useUnyohub, setUseUnyohub] = useState(false);
|
const [useUnyohub, setUseUnyohub] = useState(false);
|
||||||
const [useElesite, setUseElesite] = useState(false);
|
const [useElesite, setUseElesite] = useState(false);
|
||||||
|
|
||||||
@@ -177,6 +179,7 @@ export const DataSourceSettings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleElesite = (value: boolean) => {
|
const handleToggleElesite = (value: boolean) => {
|
||||||
|
if (!canUseElesite) return;
|
||||||
setUseElesite(value);
|
setUseElesite(value);
|
||||||
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
|
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
|
||||||
};
|
};
|
||||||
@@ -191,13 +194,7 @@ export const DataSourceSettings = () => {
|
|||||||
position: "left",
|
position: "left",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!canAccess ? (
|
<ScrollView style={[styles.content, { backgroundColor: colors.backgroundSecondary }]} contentContainerStyle={styles.contentInner}>
|
||||||
<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}>
|
|
||||||
<Text style={[styles.sectionTitle, { color: colors.textTertiary }]}>外部データソース</Text>
|
<Text style={[styles.sectionTitle, { color: colors.textTertiary }]}>外部データソース</Text>
|
||||||
|
|
||||||
<DataSourceAccordionCard
|
<DataSourceAccordionCard
|
||||||
@@ -215,20 +212,22 @@ export const DataSourceSettings = () => {
|
|||||||
linkUrl="https://unyohub.2pd.jp/railroad_shikoku/"
|
linkUrl="https://unyohub.2pd.jp/railroad_shikoku/"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataSourceAccordionCard
|
{canUseElesite && (
|
||||||
logo={ELESITE_LOGO_PNG}
|
<DataSourceAccordionCard
|
||||||
accentColor="#44bb44"
|
logo={ELESITE_LOGO_PNG}
|
||||||
title="えれサイト"
|
accentColor="#44bb44"
|
||||||
tagline="コミュニティによる列車運用情報サービス"
|
title="えれサイト"
|
||||||
enabled={useElesite}
|
tagline="コミュニティによる列車運用情報サービス"
|
||||||
onToggle={handleToggleElesite}
|
enabled={useElesite}
|
||||||
description={
|
onToggle={handleToggleElesite}
|
||||||
"えれサイトは、鉄道運用情報を共有するためのサイトです。皆様からの投稿を通じて、鉄道運行に関する情報を共有するサイトです。JR 四国の特急・普通列車を中心に対応しています。\n\nデータがある列車では地図上に緑色の「E」バッジが表示され、列車情報画面の編成表示も更新されます。"
|
description={
|
||||||
}
|
"えれサイトは、鉄道運用情報を共有するためのサイトです。皆様からの投稿を通じて、鉄道運行に関する情報を共有するサイトです。JR 四国の特急・普通列車を中心に対応しています。\n\nデータがある列車では地図上に緑色の「E」バッジが表示され、列車情報画面の編成表示も更新されます。"
|
||||||
features={ELESITE_FEATURES}
|
}
|
||||||
linkLabel="elesite-next.com を開く"
|
features={ELESITE_FEATURES}
|
||||||
linkUrl="https://www.elesite-next.com/"
|
linkLabel="elesite-next.com を開く"
|
||||||
/>
|
linkUrl="https://www.elesite-next.com/"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<View style={[styles.infoSection, { backgroundColor: colors.backgroundTertiary }]}>
|
<View style={[styles.infoSection, { backgroundColor: colors.backgroundTertiary }]}>
|
||||||
<Text style={[styles.infoText, { color: colors.textCaution }]}>
|
<Text style={[styles.infoText, { color: colors.textCaution }]}>
|
||||||
@@ -238,32 +237,11 @@ export const DataSourceSettings = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
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: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const LauncherIconSettings = () => {
|
|||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "cokumn",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
backgroundColor: "#DDDDDD",
|
backgroundColor: "#DDDDDD",
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export const LayoutSettings = ({
|
|||||||
<SimpleSwitch
|
<SimpleSwitch
|
||||||
bool={usePDFView}
|
bool={usePDFView}
|
||||||
setBool={setUsePDFView}
|
setBool={setUsePDFView}
|
||||||
color="red"
|
|
||||||
str="時刻表PDFをアプリの外で表示する"
|
str="時刻表PDFをアプリの外で表示する"
|
||||||
/>
|
/>
|
||||||
</SwitchArea>
|
</SwitchArea>
|
||||||
@@ -110,7 +109,6 @@ export const LayoutSettings = ({
|
|||||||
<SimpleSwitch
|
<SimpleSwitch
|
||||||
bool={trainPosition}
|
bool={trainPosition}
|
||||||
setBool={setTrainPosition}
|
setBool={setTrainPosition}
|
||||||
color="red"
|
|
||||||
str="列車の現在地表示/ジャンプ"
|
str="列車の現在地表示/ジャンプ"
|
||||||
/>
|
/>
|
||||||
</SwitchArea>
|
</SwitchArea>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import TouchableScale from "react-native-touchable-scale";
|
|||||||
import { SwitchArea } from "../atom/SwitchArea";
|
import { SwitchArea } from "../atom/SwitchArea";
|
||||||
import { useNotification } from "../../stateBox/useNotifications";
|
import { useNotification } from "../../stateBox/useNotifications";
|
||||||
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
||||||
import { useTrainMenu } from "../../stateBox/useTrainMenu";
|
|
||||||
import { Asset } from "expo-asset";
|
import { Asset } from "expo-asset";
|
||||||
import {
|
import {
|
||||||
useAudioPlayer,
|
useAudioPlayer,
|
||||||
@@ -36,9 +35,8 @@ export const SettingTopPage = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const { expoPushToken } = useNotification();
|
const { expoPushToken } = useNotification();
|
||||||
const { updatePermission, dataSourcePermission } = useTrainMenu();
|
|
||||||
const { colors, fixed } = useThemeColors();
|
const { colors, fixed } = useThemeColors();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation<any>();
|
||||||
|
|
||||||
// expo-asset でローカルパスを取得し、expo-audio に渡す
|
// expo-asset でローカルパスを取得し、expo-audio に渡す
|
||||||
// (SDK 52 の expo-audio は file:// URI を正しく処理できないため prefix を除去)
|
// (SDK 52 の expo-audio は file:// URI を正しく処理できないため prefix を除去)
|
||||||
@@ -95,9 +93,6 @@ export const SettingTopPage = ({
|
|||||||
}
|
}
|
||||||
}, [previewPlayer]);
|
}, [previewPlayer]);
|
||||||
|
|
||||||
// admin またはいずれかのソース権限を持つ場合のみ表示
|
|
||||||
const canAccessDataSourceSettings =
|
|
||||||
updatePermission || Object.values(dataSourcePermission).some(Boolean);
|
|
||||||
return (
|
return (
|
||||||
<View style={{ height: "100%", backgroundColor: fixed.primary }}>
|
<View style={{ height: "100%", backgroundColor: fixed.primary }}>
|
||||||
<SheetHeaderItem title="アプリの設定画面" LeftItem={{
|
<SheetHeaderItem title="アプリの設定画面" LeftItem={{
|
||||||
@@ -182,14 +177,12 @@ export const SettingTopPage = ({
|
|||||||
navigation.navigate("setting", { screen: "LayoutSettings" })
|
navigation.navigate("setting", { screen: "LayoutSettings" })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{canAccessDataSourceSettings && (
|
<SettingList
|
||||||
<SettingList
|
string="情報ソース設定"
|
||||||
string="情報ソース設定"
|
onPress={() =>
|
||||||
onPress={() =>
|
navigation.navigate("setting", { screen: "DataSourceSettings" })
|
||||||
navigation.navigate("setting", { screen: "DataSourceSettings" })
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<SettingList
|
<SettingList
|
||||||
string="アイコン設定"
|
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();
|
const { colors } = useThemeColors();
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
|
// @ts-ignore: TouchableScale props passed through Component
|
||||||
activeScale={0.95}
|
activeScale={0.95}
|
||||||
Component={TouchableScale}
|
Component={TouchableScale}
|
||||||
bottomDivider
|
bottomDivider
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const SheetHeaderItem: FC<Props> = (props) => {
|
|||||||
type SideItemProps = {
|
type SideItemProps = {
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
position: "left" | "right";
|
position?: "left" | "right";
|
||||||
};
|
};
|
||||||
const SideItem: FC<SideItemProps> = ({ onPress, title, position }) => {
|
const SideItem: FC<SideItemProps> = ({ onPress, title, position }) => {
|
||||||
const { fixed } = useThemeColors();
|
const { fixed } = useThemeColors();
|
||||||
|
|||||||
@@ -11,7 +11,18 @@ export const SwitchArea = ({
|
|||||||
trueText,
|
trueText,
|
||||||
falseValue = false,
|
falseValue = false,
|
||||||
trueValue = true,
|
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();
|
const { colors } = useThemeColors();
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import { useThemeColors } from "@/lib/theme";
|
|||||||
export default function TrainMenu({ style }) {
|
export default function TrainMenu({ style }) {
|
||||||
const { fixed } = useThemeColors();
|
const { fixed } = useThemeColors();
|
||||||
const { webview } = useCurrentTrain();
|
const { webview } = useCurrentTrain();
|
||||||
const mapRef = useRef();
|
const mapRef = useRef<any>(null);
|
||||||
const { navigate, goBack } = useNavigation();
|
const { navigate, goBack } = useNavigation<any>();
|
||||||
const [stationPin, setStationPin] = useState([]);
|
const [stationPin, setStationPin] = useState([]);
|
||||||
const {
|
const {
|
||||||
selectedLine,
|
selectedLine,
|
||||||
@@ -81,7 +81,7 @@ export default function TrainMenu({ style }) {
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
width: "100vw",
|
width: "100%" as any,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export const EachData: FC<Props> = (props) => {
|
|||||||
openStationACFromEachTrainInfo,
|
openStationACFromEachTrainInfo,
|
||||||
from: "LED",
|
from: "LED",
|
||||||
};
|
};
|
||||||
// @ts-expect-error - SheetManager payload type is too restrictive
|
|
||||||
SheetManager.show("EachTrainInfo", { payload });
|
SheetManager.show("EachTrainInfo", { payload });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useThemeColors } from "@/lib/theme";
|
|||||||
export const SwitchBox = (props) => {
|
export const SwitchBox = (props) => {
|
||||||
const { value, setValue, setKey, title } = props;
|
const { value, setValue, setKey, title } = props;
|
||||||
const { fixed } = useThemeColors();
|
const { fixed } = useThemeColors();
|
||||||
const textStyle = {
|
const textStyle: any = {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type Props = {
|
|||||||
progress: number;
|
progress: number;
|
||||||
};
|
};
|
||||||
export const LottieDelayView: FC<Props> = ({ progress }) => {
|
export const LottieDelayView: FC<Props> = ({ progress }) => {
|
||||||
const lottieRef = useRef<LottieView>();
|
const lottieRef = useRef<LottieView>(null);
|
||||||
const [progressState, setProgressState] = useState(undefined);
|
const [progressState, setProgressState] = useState(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (progress == 0) {
|
if (progress == 0) {
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default function Sign(props) {
|
|||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
下帯: {
|
下帯: {
|
||||||
position: "absolute",
|
position: "absolute" as const,
|
||||||
bottom: "8%",
|
bottom: "8%",
|
||||||
left: "0%",
|
left: "0%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -149,7 +149,7 @@ export default function Sign(props) {
|
|||||||
backgroundColor: fixed.primary,
|
backgroundColor: fixed.primary,
|
||||||
},
|
},
|
||||||
下帯B: {
|
下帯B: {
|
||||||
position: "absolute",
|
position: "absolute" as const,
|
||||||
bottom: "0%",
|
bottom: "0%",
|
||||||
left: "0%",
|
left: "0%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -157,28 +157,28 @@ export default function Sign(props) {
|
|||||||
backgroundColor: "#454545",
|
backgroundColor: "#454545",
|
||||||
},
|
},
|
||||||
JRStyle: {
|
JRStyle: {
|
||||||
position: "absolute",
|
position: "absolute" as const,
|
||||||
top: "2%",
|
top: "2%",
|
||||||
left: "2%",
|
left: "2%",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold" as const,
|
||||||
fontSize: parseInt("25%"),
|
fontSize: parseInt("25%"),
|
||||||
color: lightColors.textAccent,
|
color: lightColors.textAccent,
|
||||||
},
|
},
|
||||||
下帯内容: {
|
下帯内容: {
|
||||||
position: "absolute",
|
position: "absolute" as const,
|
||||||
bottom: "8%",
|
bottom: "8%",
|
||||||
height: "27%",
|
height: "27%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center" as const,
|
||||||
flexDirection: "column",
|
flexDirection: "column" as const,
|
||||||
},
|
},
|
||||||
下帯内容B: {
|
下帯内容B: {
|
||||||
position: "absolute",
|
position: "absolute" as const,
|
||||||
bottom: "0%",
|
bottom: "0%",
|
||||||
height: "26%",
|
height: "26%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center" as const,
|
||||||
flexDirection: "column",
|
flexDirection: "column" as const,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,9 +222,9 @@ export default function Sign(props) {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text style={styleSheet.JRStyle}>JR</Text>
|
<Text style={styleSheet.JRStyle as any}>JR</Text>
|
||||||
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"]} />
|
<View style={styleSheet[isMatsuyama ? "下帯B" : "下帯"] as any} />
|
||||||
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"]}>
|
<View style={styleSheet[isMatsuyama ? "下帯内容B" : "下帯内容"] as any}>
|
||||||
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
|
<NextPreStationLine {...{ nexStation, preStation, isMatsuyama }} />
|
||||||
</View>
|
</View>
|
||||||
<AddressText {...{ currentStation: currentStationData, isMatsuyama }} />
|
<AddressText {...{ currentStation: currentStationData, isMatsuyama }} />
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type props = {
|
|||||||
export const StationNumberMaker: FC<props> = (props) => {
|
export const StationNumberMaker: FC<props> = (props) => {
|
||||||
const { currentStation, useEach = false, singleSize } = props;
|
const { currentStation, useEach = false, singleSize } = props;
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const getTop = (array: number[], index: number) => {
|
const getTop = (array: StationProps[], index: number) => {
|
||||||
if (array.length == 1) return 20;
|
if (array.length == 1) return 20;
|
||||||
else if (index == 0) return 5;
|
else if (index == 0) return 5;
|
||||||
else if (index == 1) return 35;
|
else if (index == 1) return 35;
|
||||||
|
|||||||
89
docs/changelog-6.2.1.1-to-7.0beta-features.md
Normal file
89
docs/changelog-6.2.1.1-to-7.0beta-features.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# 6.2.1.1 -> 7.0beta 更新ログ(機能説明フォーカス版)
|
||||||
|
|
||||||
|
## このバージョンで何が良くなったか
|
||||||
|
7.0beta では、列車情報アプリとしての実用性を高めるために、外部データ連携・通知/追跡・FeliCa・ウィジェット・操作性をまとめて強化しました。
|
||||||
|
|
||||||
|
特に「情報の鮮度」「追跡の継続性」「日常利用のしやすさ」に効く改善が多いアップデートです。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 鉄道運用Hub(Unyohub)連携の強化
|
||||||
|
### 実装/更新された機能
|
||||||
|
- 外部データ連携の表示品質を向上。
|
||||||
|
- 最終更新時刻の表示や鮮度判定(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`
|
||||||
157
docs/changelog-6.2.1.1-to-7.0beta.md
Normal file
157
docs/changelog-6.2.1.1-to-7.0beta.md
Normal 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. 鉄道運用Hub(Unyohub)連携の追加・強化
|
||||||
|
- 連携データの表示品質を改善:
|
||||||
|
- 日付整形、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
|
||||||
@@ -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;
|
const stopStationIDListFiltered = isExcludeStopStation ? stopStationIDList.filter((d,index,array)=>d[0] !== array[index == 0 ? index : index -1][0]):stopStationIDList;
|
||||||
if (!stopStationIDListFiltered) return [];
|
if (!stopStationIDListFiltered) return [];
|
||||||
// arrayが二次元配列だったら早期リターン
|
// arrayが二次元配列だったら早期リターン
|
||||||
if (!array instanceof Array) return [];
|
if (!(array instanceof Array)) return [];
|
||||||
if (!array) return [];
|
if (!array) return [];
|
||||||
if (array[0] instanceof Array) return [];
|
if (array[0] instanceof Array) return [];
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const initIcon = (
|
|||||||
return ({ focused, color, size }) => (
|
return ({ focused, color, size }) => (
|
||||||
<>
|
<>
|
||||||
{!!tabBarBadge && <Badge tabBarBadge={tabBarBadge} isInfo={isInfo} />}
|
{!!tabBarBadge && <Badge tabBarBadge={tabBarBadge} isInfo={isInfo} />}
|
||||||
<IconComponent name={name} size={30} color={color} />
|
<IconComponent name={name as any} size={30} color={color} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type State = "RUNNING" | "STOPPED";
|
|||||||
type Fn = () => void;
|
type Fn = () => void;
|
||||||
|
|
||||||
export const useInterval = (fn: Fn, interval: number, autostart = true, keepAliveInBackground = false) => {
|
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");
|
const [state, setState] = useState("RUNNING");
|
||||||
// ユーザー操作によるSTOP(AppStateによる一時停止と区別する)
|
// ユーザー操作によるSTOP(AppStateによる一時停止と区別する)
|
||||||
const userStoppedRef = useRef(!autostart);
|
const userStoppedRef = useRef(!autostart);
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ const initialState = {
|
|||||||
getPosition: ((e) => {}) as (e: trainDataType) => string[] | undefined,
|
getPosition: ((e) => {}) as (e: trainDataType) => string[] | undefined,
|
||||||
fixedPositionSize: 80,
|
fixedPositionSize: 80,
|
||||||
setFixedPositionSize: (e) => {},
|
setFixedPositionSize: (e) => {},
|
||||||
liveNotificationActive: false,
|
|
||||||
setLiveNotificationActive: (e) => {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type initialStateType = {
|
type initialStateType = {
|
||||||
@@ -61,8 +59,6 @@ type initialStateType = {
|
|||||||
getPosition: (e: trainDataType) => string[] | undefined;
|
getPosition: (e: trainDataType) => string[] | undefined;
|
||||||
fixedPositionSize: number;
|
fixedPositionSize: number;
|
||||||
setFixedPositionSize: (e: number) => void;
|
setFixedPositionSize: (e: number) => void;
|
||||||
liveNotificationActive: boolean;
|
|
||||||
setLiveNotificationActive: (e: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CurrentTrainContext = createContext<initialStateType>(initialState);
|
const CurrentTrainContext = createContext<initialStateType>(initialState);
|
||||||
@@ -93,7 +89,6 @@ export const CurrentTrainProvider: FC<props> = ({ children }) => {
|
|||||||
value: null,
|
value: null,
|
||||||
});
|
});
|
||||||
const [fixedPositionSize, setFixedPositionSize] = useState(80);
|
const [fixedPositionSize, setFixedPositionSize] = useState(80);
|
||||||
const [liveNotificationActive, setLiveNotificationActive] = useState(false);
|
|
||||||
const [_, setIntervalState] = useInterval(() => {
|
const [_, setIntervalState] = useInterval(() => {
|
||||||
if (!webview.current) return;
|
if (!webview.current) return;
|
||||||
if (fixedPosition.type == "station") {
|
if (fixedPosition.type == "station") {
|
||||||
@@ -352,8 +347,6 @@ export const CurrentTrainProvider: FC<props> = ({ children }) => {
|
|||||||
nearestStationID,
|
nearestStationID,
|
||||||
fixedPositionSize,
|
fixedPositionSize,
|
||||||
setFixedPositionSize,
|
setFixedPositionSize,
|
||||||
liveNotificationActive,
|
|
||||||
setLiveNotificationActive,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { AS } from "../storageControl";
|
|||||||
import { STORAGE_KEYS } from "@/constants";
|
import { STORAGE_KEYS } from "@/constants";
|
||||||
import { API_ENDPOINTS } from "@/constants";
|
import { API_ENDPOINTS } from "@/constants";
|
||||||
import type { ElesiteResponse, ElesiteData } from "@/types/unyohub";
|
import type { ElesiteResponse, ElesiteData } from "@/types/unyohub";
|
||||||
|
import { useTrainMenu } from "@/stateBox/useTrainMenu";
|
||||||
|
|
||||||
type ElesiteHook = {
|
type ElesiteHook = {
|
||||||
/** えれサイト使用設定 */
|
/** えれサイト使用設定 */
|
||||||
@@ -18,13 +19,15 @@ type ElesiteHook = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useElesite = (): ElesiteHook => {
|
export const useElesite = (): ElesiteHook => {
|
||||||
|
const { dataSourcePermission, updatePermission } = useTrainMenu();
|
||||||
|
const canUseElesite = updatePermission || dataSourcePermission.elesite;
|
||||||
const [useElesite, setUseElesiteState] = useState(false);
|
const [useElesite, setUseElesiteState] = useState(false);
|
||||||
const [elesiteData, setElesiteData] = useState<ElesiteResponse>([]);
|
const [elesiteData, setElesiteData] = useState<ElesiteResponse>([]);
|
||||||
|
|
||||||
// 初期読み込み
|
// 初期読み込み
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
AS.getItem(STORAGE_KEYS.USE_ELESITE).then((value) => {
|
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) => {
|
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(() => {
|
useEffect(() => {
|
||||||
@@ -104,8 +114,9 @@ export const useElesite = (): ElesiteHook => {
|
|||||||
|
|
||||||
// 設定を更新
|
// 設定を更新
|
||||||
const setUseElesite = (value: boolean) => {
|
const setUseElesite = (value: boolean) => {
|
||||||
setUseElesiteState(value);
|
const next = value && canUseElesite;
|
||||||
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
|
setUseElesiteState(next);
|
||||||
|
AS.setItem(STORAGE_KEYS.USE_ELESITE, next.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ Notifications.setNotificationHandler({
|
|||||||
shouldShowAlert: true,
|
shouldShowAlert: true,
|
||||||
shouldPlaySound: true,
|
shouldPlaySound: true,
|
||||||
shouldSetBadge: true,
|
shouldSetBadge: true,
|
||||||
|
shouldShowBanner: true,
|
||||||
|
shouldShowList: true,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type initialStateType = {
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
originalStationList: [[]],
|
originalStationList: [[]],
|
||||||
setOriginalStationList: () => {},
|
setOriginalStationList: () => {},
|
||||||
getStationData: () => {},
|
getStationData: () => [],
|
||||||
stationList: [],
|
stationList: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ const initialState = {
|
|||||||
updatePermission: false,
|
updatePermission: false,
|
||||||
setUpdatePermission: (e) => {},
|
setUpdatePermission: (e) => {},
|
||||||
/** 各情報ソースの利用権限 */
|
/** 各情報ソースの利用権限 */
|
||||||
dataSourcePermission: { unyohub: false } as { unyohub: boolean },
|
dataSourcePermission: { unyohub: false, elesite: false } as {
|
||||||
|
unyohub: boolean;
|
||||||
|
elesite: boolean;
|
||||||
|
},
|
||||||
injectJavascript: "",
|
injectJavascript: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,11 +69,14 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
|||||||
|
|
||||||
//更新権限所有確認・情報ソース別利用権限(将来ロールが増えたらここに足す)
|
//更新権限所有確認・情報ソース別利用権限(将来ロールが増えたらここに足す)
|
||||||
const [updatePermission, setUpdatePermission] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!expoPushToken) return;
|
if (!expoPushToken) return;
|
||||||
fetch(
|
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) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -78,6 +84,7 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
|||||||
setUpdatePermission(role === "administrator");
|
setUpdatePermission(role === "administrator");
|
||||||
setDataSourcePermission({
|
setDataSourcePermission({
|
||||||
unyohub: role === "administrator" || role === "unyoHubEditor",
|
unyohub: role === "administrator" || role === "unyoHubEditor",
|
||||||
|
elesite: role === "administrator" || role === "eleSiteEditor",
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
@@ -115,19 +122,49 @@ export const TrainMenuProvider: FC<props> = ({ children }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
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.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デザインベーススイッチ
|
//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スイッチ
|
//鉄道運用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 (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user