105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
endTrainFollowActivity,
|
|
getActiveTrainFollowActivities,
|
|
isAvailable,
|
|
startTrainFollowActivity,
|
|
TrainFollowParams,
|
|
TrainFollowState,
|
|
updateTrainFollowActivity,
|
|
} from "expo-live-activity";
|
|
|
|
type Status = "idle" | "active" | "loading" | "error";
|
|
|
|
type UseTrainFollowActivityResult = {
|
|
/** Live Activity が利用可能なデバイス・OS かどうか */
|
|
available: boolean;
|
|
/** 現在の状態 */
|
|
status: Status;
|
|
/** エラーメッセージ (status === "error" のとき) */
|
|
error: string | null;
|
|
/** 現在のアクティビティ ID (active のときのみ非 null) */
|
|
activityId: string | null;
|
|
/** 列車追従 Live Activity を開始する */
|
|
start: (params: TrainFollowParams) => Promise<void>;
|
|
/** Live Activity の状態を更新する */
|
|
update: (state: TrainFollowState) => Promise<void>;
|
|
/** Live Activity を終了する */
|
|
end: () => Promise<void>;
|
|
};
|
|
|
|
/**
|
|
* 列車追従 Live Activity を管理するフック。
|
|
* アンマウント時に自動で Live Activity を終了する。
|
|
*/
|
|
export function useTrainFollowActivity(): UseTrainFollowActivityResult {
|
|
const [available] = useState(() => isAvailable());
|
|
const [status, setStatus] = useState<Status>("idle");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [activityId, setActivityId] = useState<string | null>(() => {
|
|
// アプリ再起動後など、既存のアクティビティを復元する
|
|
const ids = getActiveTrainFollowActivities();
|
|
return ids.length > 0 ? ids[0] : null;
|
|
});
|
|
|
|
// マウント時に既存アクティビティがあれば active に設定
|
|
useEffect(() => {
|
|
if (activityId) setStatus("active");
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
// アンマウント時に自動終了
|
|
const activityIdRef = useRef(activityId);
|
|
useEffect(() => {
|
|
activityIdRef.current = activityId;
|
|
}, [activityId]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (activityIdRef.current) {
|
|
endTrainFollowActivity(activityIdRef.current).catch(() => {});
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
const start = useCallback(async (params: TrainFollowParams) => {
|
|
if (!available) return;
|
|
setStatus("loading");
|
|
setError(null);
|
|
try {
|
|
const id = await startTrainFollowActivity(params);
|
|
setActivityId(id);
|
|
setStatus("active");
|
|
} catch (e: unknown) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
setError(msg);
|
|
setStatus("error");
|
|
}
|
|
}, [available]);
|
|
|
|
const update = useCallback(async (state: TrainFollowState) => {
|
|
if (!activityIdRef.current) return;
|
|
try {
|
|
await updateTrainFollowActivity(activityIdRef.current, state);
|
|
} catch (e: unknown) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
setError(msg);
|
|
setStatus("error");
|
|
}
|
|
}, []);
|
|
|
|
const end = useCallback(async () => {
|
|
if (!activityIdRef.current) return;
|
|
try {
|
|
await endTrainFollowActivity(activityIdRef.current);
|
|
setActivityId(null);
|
|
setStatus("idle");
|
|
} catch (e: unknown) {
|
|
const msg = e instanceof Error ? e.message : String(e);
|
|
setError(msg);
|
|
setStatus("error");
|
|
}
|
|
}, []);
|
|
|
|
return { available, status, error, activityId, start, update, end };
|
|
}
|