feat: add hooks for managing Live Activities for station locking and train following
This commit is contained in:
104
lib/useStationLockActivity.ts
Normal file
104
lib/useStationLockActivity.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
endStationLockActivity,
|
||||
getActiveStationLockActivities,
|
||||
isAvailable,
|
||||
startStationLockActivity,
|
||||
StationLockParams,
|
||||
StationLockState,
|
||||
updateStationLockActivity,
|
||||
} from "expo-live-activity";
|
||||
|
||||
type Status = "idle" | "active" | "loading" | "error";
|
||||
|
||||
type UseStationLockActivityResult = {
|
||||
/** Live Activity が利用可能なデバイス・OS かどうか */
|
||||
available: boolean;
|
||||
/** 現在の状態 */
|
||||
status: Status;
|
||||
/** エラーメッセージ (status === "error" のとき) */
|
||||
error: string | null;
|
||||
/** 現在のアクティビティ ID (active のときのみ非 null) */
|
||||
activityId: string | null;
|
||||
/** 駅ロック Live Activity を開始する */
|
||||
start: (params: StationLockParams) => Promise<void>;
|
||||
/** Live Activity の状態を更新する */
|
||||
update: (state: StationLockState) => Promise<void>;
|
||||
/** Live Activity を終了する */
|
||||
end: () => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 駅ロック Live Activity を管理するフック。
|
||||
* アンマウント時に自動で Live Activity を終了する。
|
||||
*/
|
||||
export function useStationLockActivity(): UseStationLockActivityResult {
|
||||
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 = getActiveStationLockActivities();
|
||||
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) {
|
||||
endStationLockActivity(activityIdRef.current).catch(() => {});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const start = useCallback(async (params: StationLockParams) => {
|
||||
if (!available) return;
|
||||
setStatus("loading");
|
||||
setError(null);
|
||||
try {
|
||||
const id = await startStationLockActivity(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: StationLockState) => {
|
||||
if (!activityIdRef.current) return;
|
||||
try {
|
||||
await updateStationLockActivity(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 endStationLockActivity(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 };
|
||||
}
|
||||
104
lib/useTrainFollowActivity.ts
Normal file
104
lib/useTrainFollowActivity.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
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 };
|
||||
}
|
||||
@@ -3086,6 +3086,9 @@ expo-linking@~55.0.8:
|
||||
expo-constants "~55.0.8"
|
||||
invariant "^2.2.4"
|
||||
|
||||
"expo-live-activity@file:./modules/expo-live-activity":
|
||||
version "0.1.0"
|
||||
|
||||
expo-localization@~55.0.9:
|
||||
version "55.0.9"
|
||||
resolved "https://registry.yarnpkg.com/expo-localization/-/expo-localization-55.0.9.tgz#00b4150d24aa443d9bc210bee3bdae8556fa7730"
|
||||
|
||||
Reference in New Issue
Block a user