import { FC, useRef, useState, useCallback } 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"; export const ExGridView: FC<{ data: { trainNumber: string; array: string; name: string; type: string; time: string; }[]; }> = ({ data }) => { const groupedData: { [d: number]: { trainNumber: string; array: string; name: string; type: 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: string[] = ["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; } // if (!groupedData[hour]) { // groupedData[hour] = []; // groupKeys.push(hour); // } groupedData[hour].push({ ...item, time: `${hour}:${minute}`, isOperating }); }); // ドラッグ位置を保持する共有値 const widthX = useSharedValue(width); const savedWidthX = useSharedValue(width); const isChanging = useSharedValue(false); const animationProgress = useSharedValue(0); // アニメーション進行度 const [scrollEnabled, setScrollEnabled] = useState(true); const scrollRef = 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", })); 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) : [] } > {groupKeys.map((hour) => [ {hour}時台 , {groupedData[hour].map((d, i, array) => ( ))} , ])} ); };