Files
jrshikoku/components/Settings/DataSourceSettings.tsx

426 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from "react";
import { View, Text, ScrollView, StyleSheet, Image, TouchableOpacity, Linking } from "react-native";
import { Switch } from "react-native-elements";
import { useNavigation } from "@react-navigation/native";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { SheetHeaderItem } from "@/components/atom/SheetHeaderItem";
import { AS } from "../../storageControl";
import { STORAGE_KEYS } from "@/constants";
import { useTrainMenu } from "@/stateBox/useTrainMenu";
const HUB_LOGO_PNG = require("@/assets/icons/hub_logo.png");
const ELESITE_LOGO_PNG = require("@/assets/icons/elesite_logo.png");
/* ------------------------------------------------------------------ */
/* DataSourceAccordionCard */
/* ------------------------------------------------------------------ */
type Feature = { icon: string; label: string; text: string };
type DataSourceAccordionCardProps = {
/** ロゴ画像 (require) */
logo: any;
/** アクセントカラー */
accentColor: string;
/** データソース名 */
title: string;
/** 1行サブタイトル */
tagline: string;
/** スイッチの値 */
enabled: boolean;
/** スイッチ変更ハンドラ */
onToggle: (v: boolean) => void;
/** 説明文 */
description: string;
/** 機能リスト */
features: Feature[];
/** フッターリンクラベル */
linkLabel: string;
/** フッターリンク URL */
linkUrl: string;
};
const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
logo,
accentColor,
title,
tagline,
enabled,
onToggle,
description,
features,
linkLabel,
linkUrl,
detailLabel,
}) => {
const [expanded, setExpanded] = useState(false);
return (
<View style={[styles.accordionCard, enabled && styles.accordionCardEnabled]}>
{/* ── ヘッダー行(常時表示) ── */}
<View style={styles.accordionHeader}>
{/* 左:ロゴ */}
<Image source={logo} style={styles.accordionLogo} />
{/* 中央:タイトル+タグライン */}
<View style={styles.accordionTitles}>
<Text style={styles.accordionTitle}>{title}</Text>
<Text style={styles.accordionTagline}>{tagline}</Text>
</View>
{/* 右:スイッチ */}
<Switch
value={enabled}
onValueChange={onToggle}
color={accentColor}
style={styles.accordionSwitch}
/>
</View>
{/* スイッチ状態テキスト */}
<View style={styles.accordionStatusRow}>
<View style={[styles.statusDot, { backgroundColor: enabled ? accentColor : "#ccc" }]} />
<Text style={[styles.statusText, { color: enabled ? accentColor : "#aaa" }]}>
{enabled ? "有効 — 編成データを取得します" : "無効 — データを取得しません"}
</Text>
</View>
{/* ── 展開トリガー ── */}
<TouchableOpacity
style={styles.accordionToggleRow}
onPress={() => setExpanded((v) => !v)}
activeOpacity={0.6}
>
<Text style={styles.accordionToggleLabel}>
{expanded ? "詳細を閉じる" : (detailLabel ?? `${title} について`)}
</Text>
<MaterialCommunityIcons
name={expanded ? "chevron-up" : "chevron-down"}
size={16}
color="#888"
/>
</TouchableOpacity>
{/* ── 展開コンテンツ ── */}
{expanded && (
<View style={styles.accordionBody}>
{/* 説明文 */}
<Text style={styles.bodyDesc}>{description}</Text>
{/* 機能リスト */}
<View style={styles.bodyFeatures}>
{features.map((f) => (
<View key={f.icon} style={styles.featureRow}>
<View style={styles.featureIcon}>
<MaterialCommunityIcons name={f.icon as any} size={14} color="#444" />
</View>
<Text style={styles.featureLabel}>{f.label}</Text>
<Text style={styles.featureText}>{f.text}</Text>
</View>
))}
</View>
{/* リンク */}
<TouchableOpacity
style={styles.bodyLink}
onPress={() => Linking.openURL(linkUrl)}
activeOpacity={0.7}
>
<MaterialCommunityIcons name="open-in-new" size={13} color="#555" />
<Text style={styles.bodyLinkText}>{linkLabel}</Text>
</TouchableOpacity>
</View>
)}
</View>
);
};
/* ------------------------------------------------------------------ */
/* 定数 */
/* ------------------------------------------------------------------ */
const UNYOHUB_FEATURES: Feature[] = [
{ icon: "calendar-today", label: "運用データ", text: "本日・過去数日から投稿があった運用の継続予測運用情報を表示" },
{ icon: "map-outline", label: "対象エリア", text: "JR四国全線" },
{ icon: "train", label: "対象運用", text: "JR四国管内営業列車及び貨物列車に対応、臨時列車/突発運用は非対応" },
{ icon: "plus", label: "追加機能", text: "前日、当日、翌日の運用の投稿が可能" },
];
const ELESITE_FEATURES: Feature[] = [
{ icon: "calendar-today", label: "運用データ", text: "当日に報告のあった運用情報のみ表示" },
{ icon: "map-outline", label: "対象エリア", text: "予讃線/瀬戸大橋線(なお直通している特急などの列番は含みます)" },
{ icon: "train", label: "対象運用", text: "JR四国管内営業列車対応、臨時列車/突発運用は非対応" },
];
/* ------------------------------------------------------------------ */
/* DataSourceSettings */
/* ------------------------------------------------------------------ */
export const DataSourceSettings = () => {
const navigation = useNavigation();
const { updatePermission, dataSourcePermission } = useTrainMenu();
const canAccess = updatePermission || Object.values(dataSourcePermission).some(Boolean);
const [useUnyohub, setUseUnyohub] = useState(false);
const [useElesite, setUseElesite] = useState(false);
useEffect(() => {
AS.getItem(STORAGE_KEYS.USE_UNYOHUB).then((value) => {
setUseUnyohub(value === true || value === "true");
});
AS.getItem(STORAGE_KEYS.USE_ELESITE).then((value) => {
setUseElesite(value === true || value === "true");
});
}, []);
const handleToggleUnyohub = (value: boolean) => {
setUseUnyohub(value);
AS.setItem(STORAGE_KEYS.USE_UNYOHUB, value.toString());
};
const handleToggleElesite = (value: boolean) => {
setUseElesite(value);
AS.setItem(STORAGE_KEYS.USE_ELESITE, value.toString());
};
return (
<View style={styles.container}>
<SheetHeaderItem
title="情報ソース設定"
LeftItem={{
title: "戻る",
onPress: () => navigation.goBack(),
position: "left",
}}
/>
{!canAccess ? (
<View style={styles.noPermissionContainer}>
<Text style={styles.noPermissionText}></Text>
<Text style={styles.noPermissionSubText}>Hubまたはアプリ管理者の権限が必要です</Text>
</View>
) : (
<ScrollView style={styles.content} contentContainerStyle={styles.contentInner}>
<Text style={styles.sectionTitle}></Text>
<DataSourceAccordionCard
logo={HUB_LOGO_PNG}
accentColor="#0099CC"
title="鉄道運用Hub"
tagline="コミュニティによる列車運用情報サービス"
enabled={useUnyohub}
onToggle={handleToggleUnyohub}
description={
"鉄道運用Hubはオープンソースのユーザー投稿型鉄道運用情報データベースアプリケーションです。JR 四国をはじめ全国多数の路線系統に対応しています。\n\nデータがある列車では地図上にアイコンでマークが表示され、列車情報画面の編成表示も更新されます。"
}
features={UNYOHUB_FEATURES}
linkLabel="unyohub.2pd.jp を開くJR四国"
linkUrl="https://unyohub.2pd.jp/railroad_shikoku/"
/>
<DataSourceAccordionCard
logo={ELESITE_LOGO_PNG}
accentColor="#44bb44"
title="えれサイト"
tagline="コミュニティによる列車運用情報サービス"
enabled={useElesite}
onToggle={handleToggleElesite}
description={
"えれサイトは、鉄道運用情報を共有するためのサイトです。皆様からの投稿を通じて、鉄道運行に関する情報を共有するサイトです。JR 四国の特急・普通列車を中心に対応しています。\n\nデータがある列車では地図上に緑色の「E」バッジが表示され、列車情報画面の編成表示も更新されます。"
}
features={ELESITE_FEATURES}
linkLabel="elesite-next.com を開く"
linkUrl="https://www.elesite-next.com/"
/>
<View style={styles.infoSection}>
<Text style={styles.infoText}>
{"\n\n"}
{"\n\n"}JR四国非公式アプリが管理していないデータであるため
</Text>
</View>
</ScrollView>
)}
</View>
);
};
const styles = StyleSheet.create({
/* ── 権限なし ── */
noPermissionContainer: {
flex: 1,
backgroundColor: "#f8f8fc",
alignItems: "center",
justifyContent: "center",
padding: 30,
gap: 10,
},
noPermissionText: {
fontSize: 16,
fontWeight: "bold",
color: "#333",
textAlign: "center",
},
noPermissionSubText: {
fontSize: 13,
color: "#666",
textAlign: "center",
},
/* ── レイアウト ── */
container: {
flex: 1,
backgroundColor: "#0099CC",
},
content: {
flex: 1,
backgroundColor: "#f8f8fc",
},
contentInner: {
paddingHorizontal: 14,
paddingBottom: 40,
gap: 12,
},
sectionTitle: {
fontSize: 13,
fontWeight: "600",
color: "#888",
letterSpacing: 0.5,
marginTop: 20,
marginLeft: 4,
},
/* ── アコーディオンカード ── */
accordionCard: {
backgroundColor: "#fff",
borderRadius: 14,
borderWidth: 1,
borderColor: "#e4e4e4",
overflow: "hidden",
},
accordionCardEnabled: {
borderColor: "#0099CC44",
},
accordionHeader: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 14,
paddingTop: 14,
paddingBottom: 6,
gap: 10,
},
accordionLogo: {
width: 40,
height: 40,
borderRadius: 8,
flexShrink: 0,
},
accordionTitles: {
flex: 1,
gap: 2,
},
accordionTitle: {
fontSize: 15,
fontWeight: "bold",
color: "#111",
},
accordionTagline: {
fontSize: 11,
color: "#888",
},
accordionSwitch: {
flexShrink: 0,
},
accordionStatusRow: {
flexDirection: "row",
alignItems: "center",
gap: 6,
paddingHorizontal: 14,
paddingBottom: 10,
},
statusDot: {
width: 7,
height: 7,
borderRadius: 4,
},
statusText: {
fontSize: 12,
fontWeight: "500",
},
/* ── 展開トリガー ── */
accordionToggleRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 14,
paddingVertical: 10,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#ebebeb",
},
accordionToggleLabel: {
fontSize: 12,
color: "#666",
fontWeight: "500",
},
/* ── 展開コンテンツ ── */
accordionBody: {
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#ebebeb",
padding: 14,
gap: 10,
backgroundColor: "#fafafa",
},
bodyDesc: {
fontSize: 12,
color: "#444",
lineHeight: 19,
},
bodyFeatures: {
gap: 7,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#e4e4e4",
paddingTop: 8,
},
featureRow: {
flexDirection: "row",
alignItems: "flex-start",
gap: 6,
},
featureIcon: {
width: 22,
alignItems: "center",
paddingTop: 1,
flexShrink: 0,
},
featureLabel: {
fontSize: 12,
fontWeight: "bold",
color: "#333",
width: 62,
flexShrink: 0,
},
featureText: {
fontSize: 12,
color: "#555",
flex: 1,
lineHeight: 17,
},
bodyLink: {
flexDirection: "row",
alignItems: "center",
gap: 5,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#e4e4e4",
paddingTop: 8,
marginTop: 2,
},
bodyLinkText: {
fontSize: 12,
color: "#555",
},
/* ── 注意書き ── */
infoSection: {
backgroundColor: "#fff3cd",
borderRadius: 10,
padding: 14,
},
infoText: {
fontSize: 13,
color: "#856404",
lineHeight: 18,
},
});