119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import React, { useCallback, useEffect, useState } from "react";
|
||
import { View, Text, ScrollView, Platform } from "react-native";
|
||
import { Switch } from "@rneui/themed";
|
||
import { useNavigation } from "@react-navigation/native";
|
||
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
|
||
import { AS } from "../../storageControl";
|
||
import { STORAGE_KEYS } from "@/constants";
|
||
import { useThemeColors } from "@/lib/theme";
|
||
import { Asset } from "expo-asset";
|
||
import { useAudioPlayer, setAudioModeAsync } from "expo-audio";
|
||
import type { AudioSource } from "expo-audio";
|
||
|
||
const previewSound = require("../../assets/sound/rikka-test.mp3");
|
||
|
||
export const SoundSettings = () => {
|
||
const { goBack } = useNavigation();
|
||
const { colors, fixed } = useThemeColors();
|
||
const [delayAnnouncement, setDelayAnnouncement] = useState(false);
|
||
|
||
// expo-asset でローカルパスを取得し、expo-audio に渡す
|
||
const [resolvedSource, setResolvedSource] = useState<AudioSource>(null);
|
||
|
||
useEffect(() => {
|
||
let mounted = true;
|
||
const resolve = async () => {
|
||
try {
|
||
const asset = Asset.fromModule(previewSound);
|
||
await asset.downloadAsync();
|
||
const localUri = asset.localUri;
|
||
|
||
if (!mounted) return;
|
||
|
||
if (localUri) {
|
||
// Android の expo-audio は file:// URI を正しく処理できないため prefix を除去
|
||
// iOS は file:// プレフィックスが必要なのでそのまま使用
|
||
const source =
|
||
Platform.OS === "android"
|
||
? { uri: localUri.replace(/^file:\/\//, "") }
|
||
: { uri: localUri };
|
||
setResolvedSource(source);
|
||
}
|
||
} catch (error) {
|
||
if (!mounted) return;
|
||
console.warn("Failed to resolve audio asset", error);
|
||
}
|
||
};
|
||
|
||
resolve();
|
||
return () => {
|
||
mounted = false;
|
||
};
|
||
}, []);
|
||
|
||
const previewPlayer = useAudioPlayer(resolvedSource);
|
||
|
||
useEffect(() => {
|
||
AS.getItem(STORAGE_KEYS.SOUND_DELAY_ANNOUNCEMENT)
|
||
.then((v) => setDelayAnnouncement(v === true || v === "true"))
|
||
.catch(() => {
|
||
// 未設定時はデフォルト値 false のまま
|
||
});
|
||
}, []);
|
||
|
||
const playPreview = useCallback(async () => {
|
||
try {
|
||
await setAudioModeAsync({
|
||
playsInSilentMode: true,
|
||
shouldPlayInBackground: false,
|
||
interruptionMode: "duckOthers",
|
||
});
|
||
if (previewPlayer.playing) previewPlayer.pause();
|
||
previewPlayer.volume = 1;
|
||
await previewPlayer.seekTo(0);
|
||
previewPlayer.play();
|
||
} catch (error) {
|
||
console.warn("Failed to play preview sound", error);
|
||
}
|
||
}, [previewPlayer]);
|
||
|
||
const handleToggle = (value: boolean) => {
|
||
setDelayAnnouncement(value);
|
||
AS.setItem(STORAGE_KEYS.SOUND_DELAY_ANNOUNCEMENT, value.toString());
|
||
if (value) {
|
||
playPreview();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<View style={{ height: "100%", backgroundColor: fixed.primary }}>
|
||
<SheetHeaderItem
|
||
title="サウンド機能(β)"
|
||
LeftItem={{ title: "< 設定", onPress: goBack }}
|
||
/>
|
||
<ScrollView style={{ flex: 1, backgroundColor: colors.background }}>
|
||
<View
|
||
style={{
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
paddingHorizontal: 15,
|
||
paddingVertical: 14,
|
||
borderBottomWidth: 1,
|
||
borderBottomColor: colors.borderSecondary ?? "#ccc",
|
||
backgroundColor: colors.surface,
|
||
}}
|
||
>
|
||
<Text style={{ flex: 1, fontSize: 16, color: colors.text }}>
|
||
駅固定モード遅延速報案内機能
|
||
</Text>
|
||
<Switch
|
||
value={delayAnnouncement}
|
||
onValueChange={handleToggle}
|
||
color={fixed.primary}
|
||
/>
|
||
</View>
|
||
</ScrollView>
|
||
</View>
|
||
);
|
||
};
|