356 lines
9.4 KiB
TypeScript
356 lines
9.4 KiB
TypeScript
import { FC, useRef, useState, useCallback, useEffect } from "react";
|
||
import {
|
||
View,
|
||
Text,
|
||
ScrollView,
|
||
useWindowDimensions,
|
||
Vibration,
|
||
} from "react-native";
|
||
import { ExGridViewItem } from "./ExGridViewItem";
|
||
import Animated, {
|
||
useAnimatedStyle,
|
||
useSharedValue,
|
||
runOnJS,
|
||
useAnimatedScrollHandler,
|
||
withTiming,
|
||
Easing,
|
||
FadeIn,
|
||
FadeOut,
|
||
BounceInUp,
|
||
FadeInUp,
|
||
FadeOutUp,
|
||
} from "react-native-reanimated";
|
||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||
import { ExGridViewTimePositionItem } from "./ExGridViewTimePositionItem";
|
||
import { useCurrentTrain } from "@/stateBox/useCurrentTrain";
|
||
import dayjs from "dayjs";
|
||
type hoge = {
|
||
trainNumber: string;
|
||
array: string;
|
||
name: string;
|
||
timeType: string;
|
||
time: string;
|
||
}[];
|
||
export const ExGridView: FC<{
|
||
data: hoge;
|
||
}> = ({ data }) => {
|
||
const groupedData: {
|
||
[d: number]: {
|
||
trainNumber: string;
|
||
array: string;
|
||
name: string;
|
||
timeType: string;
|
||
time: string;
|
||
isOperating: boolean;
|
||
}[];
|
||
} = {
|
||
"4": [],
|
||
"5": [],
|
||
"6": [],
|
||
"7": [],
|
||
"8": [],
|
||
"9": [],
|
||
"10": [],
|
||
"11": [],
|
||
"12": [],
|
||
"13": [],
|
||
"14": [],
|
||
"15": [],
|
||
"16": [],
|
||
"17": [],
|
||
"18": [],
|
||
"19": [],
|
||
"20": [],
|
||
"21": [],
|
||
"22": [],
|
||
"23": [],
|
||
"0": [],
|
||
"1": [],
|
||
"2": [],
|
||
"3": [],
|
||
};
|
||
const groupKeys = [
|
||
"4",
|
||
"5",
|
||
"6",
|
||
"7",
|
||
"8",
|
||
"9",
|
||
"10",
|
||
"11",
|
||
"12",
|
||
"13",
|
||
"14",
|
||
"15",
|
||
"16",
|
||
"17",
|
||
"18",
|
||
"19",
|
||
"20",
|
||
"21",
|
||
"22",
|
||
"23",
|
||
"0",
|
||
"1",
|
||
"2",
|
||
"3",
|
||
];
|
||
|
||
const { width } = useWindowDimensions();
|
||
const { currentTrain } = useCurrentTrain();
|
||
data.forEach((item) => {
|
||
let isOperating = false;
|
||
let [hour, minute] = dayjs()
|
||
.hour(parseInt(item.time.split(":")[0]))
|
||
.minute(parseInt(item.time.split(":")[1]))
|
||
.format("H:m")
|
||
.split(":");
|
||
if (currentTrain.findIndex((x) => x.num == item.trainNumber) != -1) {
|
||
const currentTrainTime = currentTrain.find(
|
||
(x) => x.num == item.trainNumber
|
||
)?.delay;
|
||
if (currentTrainTime != "入線") {
|
||
[hour, minute] = dayjs()
|
||
.hour(parseInt(hour))
|
||
.minute(parseInt(minute))
|
||
.add(parseInt(currentTrainTime), "minute")
|
||
.format("H:m")
|
||
.split(":");
|
||
}
|
||
isOperating = true;
|
||
}
|
||
groupedData[hour].push({ ...item, time: `${hour}:${minute}`, isOperating });
|
||
});
|
||
// ドラッグ位置を保持する共有値
|
||
const widthX = useSharedValue(width);
|
||
const savedWidthX = useSharedValue(width);
|
||
const isChanging = useSharedValue(false);
|
||
const [scrollEnabled, setScrollEnabled] = useState(true);
|
||
const scrollRef = useRef<Animated.ScrollView>(null);
|
||
const scrollRef2 = useRef<Animated.ScrollView>(null);
|
||
|
||
// ScrollViewの有効/無効を切り替える関数
|
||
const toggleScrollEnabled = useCallback((enabled: boolean) => {
|
||
setScrollEnabled(enabled);
|
||
}, []);
|
||
|
||
// パンジェスチャー(ドラッグ)のハンドラー
|
||
const pinchGesture = Gesture.Pinch()
|
||
.onUpdate((e) => {
|
||
const calc = savedWidthX.value * e.scale;
|
||
widthX.value = calc > width ? calc : width;
|
||
//runOnJS(scrollToRightEnd)();
|
||
})
|
||
.onEnd(() => {
|
||
savedWidthX.value = widthX.value;
|
||
});
|
||
|
||
const gesture = Gesture.Pan()
|
||
.minPointers(2) // 最低2本指
|
||
.maxPointers(2) // 最大2本指
|
||
.onStart(() => {
|
||
runOnJS(toggleScrollEnabled)(false);
|
||
})
|
||
.onEnd(() => {
|
||
runOnJS(toggleScrollEnabled)(true);
|
||
savedWidthX.value = widthX.value;
|
||
});
|
||
const longPressGesture = Gesture.Pan()
|
||
.minPointers(1)
|
||
.maxPointers(1)
|
||
.activateAfterLongPress(200)
|
||
.onStart(() => {
|
||
runOnJS(Vibration.vibrate)(30);
|
||
isChanging.value = true;
|
||
})
|
||
.onUpdate((e) => {
|
||
const calc = widthX.value + e.velocityY;
|
||
widthX.value = calc > width ? calc : width;
|
||
})
|
||
.onEnd(() => {
|
||
console.log("Long press ended");
|
||
isChanging.value = false;
|
||
});
|
||
|
||
// ジェスチャーを組み合わせる
|
||
const composed = Gesture.Simultaneous(
|
||
longPressGesture,
|
||
pinchGesture,
|
||
gesture
|
||
);
|
||
|
||
// アニメーションスタイル
|
||
const animatedStyle = useAnimatedStyle(() => ({
|
||
width: widthX.value,
|
||
backgroundColor: isChanging.value ? "#8adeffff" : "white",
|
||
}));
|
||
// 時ヘッダーを横にスクロールしたときの処理
|
||
const scrollX = useSharedValue(0);
|
||
const scrollHandler = useAnimatedScrollHandler({
|
||
onScroll: (event) => {
|
||
scrollX.value = event.contentOffset.x;
|
||
},
|
||
});
|
||
const stickyTextStyle = useAnimatedStyle(() => ({
|
||
transform: [{ translateX: scrollX.value }],
|
||
}));
|
||
const animatedLongPressStyle = useAnimatedStyle(() => ({
|
||
display: isChanging.value ? "flex" : "none",
|
||
}));
|
||
useEffect(() => {
|
||
const getCurrentTime = dayjs().hour();
|
||
setTimeout(() => {
|
||
const keyTime =
|
||
getCurrentTime - 4 <= 0 ? getCurrentTime + 24 : getCurrentTime;
|
||
const goTo = keyTime * 60;
|
||
if (goTo > 400) {
|
||
scrollRef2.current?.scrollTo({ y: goTo - 300, animated: true });
|
||
}
|
||
}, 400);
|
||
}, [scrollRef2]);
|
||
return (
|
||
<>
|
||
<Animated.View
|
||
style={[
|
||
{
|
||
position: "absolute",
|
||
width,
|
||
backgroundColor: "#26d1baff",
|
||
zIndex: 500,
|
||
top: 0,
|
||
},
|
||
animatedLongPressStyle,
|
||
]}
|
||
entering={FadeInUp}
|
||
exiting={FadeOutUp}
|
||
>
|
||
<Text style={{ fontSize: 30, textAlign: "center", flex: 1 }}>
|
||
↑縮小 ・ 拡大↓
|
||
</Text>
|
||
</Animated.View>
|
||
<GestureDetector gesture={composed}>
|
||
<Animated.ScrollView
|
||
horizontal
|
||
nestedScrollEnabled
|
||
pinchGestureEnabled={false}
|
||
scrollEnabled={scrollEnabled}
|
||
onScroll={scrollHandler}
|
||
onContentSizeChange={(contentWidth) => {
|
||
// 現在のスクロール位置を取得
|
||
const currentScrollX = scrollX.value;
|
||
const containerWidth = width - 50;
|
||
|
||
// コンテンツが画面からはみ出している場合のみ右端にスクロール
|
||
if (currentScrollX + containerWidth > contentWidth) {
|
||
const newScrollX = Math.max(0, contentWidth - containerWidth);
|
||
scrollRef.current?.scrollTo({ x: newScrollX, animated: true });
|
||
}
|
||
}}
|
||
ref={scrollRef}
|
||
contentContainerStyle={{
|
||
flexDirection: "column",
|
||
backgroundColor: "white",
|
||
}}
|
||
>
|
||
<Animated.View
|
||
style={[
|
||
{
|
||
width: width,
|
||
flexDirection: "row",
|
||
},
|
||
animatedStyle,
|
||
]}
|
||
>
|
||
{Array.from({ length: 60 }, (_, i) => i + 1).map((num) => {
|
||
if (num % 5 === 0) {
|
||
return (
|
||
<Text
|
||
key={num + "ExGridViewTimeLabel"}
|
||
style={{
|
||
flex: 1,
|
||
textAlign: "left",
|
||
borderRightWidth: 0.5,
|
||
borderColor: "#ccc",
|
||
flexWrap: "nowrap",
|
||
fontSize: 12,
|
||
}}
|
||
>
|
||
{num - 5}
|
||
</Text>
|
||
);
|
||
} else return <></>;
|
||
})}
|
||
<Text
|
||
style={{
|
||
textAlign: "right",
|
||
borderRightWidth: 0.5,
|
||
borderColor: "#ccc",
|
||
flexWrap: "nowrap",
|
||
fontSize: 12,
|
||
width: 50,
|
||
}}
|
||
>
|
||
(分)
|
||
</Text>
|
||
</Animated.View>
|
||
<Animated.ScrollView
|
||
style={[{ width: width }, animatedStyle]}
|
||
pinchGestureEnabled={false}
|
||
minimumZoomScale={0.5}
|
||
maximumZoomScale={3.0}
|
||
scrollEnabled={scrollEnabled}
|
||
stickyHeaderIndices={
|
||
groupKeys.at(0) ? groupKeys.map((_, i) => i * 2) : []
|
||
}
|
||
ref={scrollRef2}
|
||
>
|
||
{groupKeys.map((hour) => [
|
||
<View
|
||
style={{
|
||
padding: 5,
|
||
borderBottomWidth: 0.5,
|
||
borderTopWidth: 0.5,
|
||
borderBottomColor: "#ccc",
|
||
backgroundColor: "#f0f0f0",
|
||
}}
|
||
key={hour}
|
||
>
|
||
<Animated.Text
|
||
style={[
|
||
{
|
||
fontSize: 15,
|
||
zIndex: 1,
|
||
marginLeft: 0,
|
||
},
|
||
stickyTextStyle,
|
||
]}
|
||
>
|
||
{hour}時台
|
||
</Animated.Text>
|
||
</View>,
|
||
<View
|
||
style={{
|
||
flexDirection: "row",
|
||
position: "relative",
|
||
height: 50,
|
||
}}
|
||
>
|
||
{groupedData[hour].map((d, i, array) => (
|
||
<ExGridViewItem
|
||
key={d.trainNumber + i}
|
||
d={d}
|
||
index={i}
|
||
width={widthX}
|
||
array={array}
|
||
/>
|
||
))}
|
||
<ExGridViewTimePositionItem width={widthX} hour={hour} />
|
||
</View>,
|
||
])}
|
||
</Animated.ScrollView>
|
||
</Animated.ScrollView>
|
||
</GestureDetector>
|
||
</>
|
||
);
|
||
};
|