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(null); const scrollRef2 = useRef(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 ( <> ↑縮小 ・ 拡大↓ { // 現在のスクロール位置を取得 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", }} > {Array.from({ length: 60 }, (_, i) => i + 1).map((num) => { if (num % 5 === 0) { return ( {num - 5} ); } else return <>; })} (分) i * 2) : [] } ref={scrollRef2} > {groupKeys.map((hour) => [ {hour}時台 , {groupedData[hour].map((d, i, array) => ( ))} , ])} ); };