fix: update useWebViewRemount to include backgroundThresholdMs option for better app state handling
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { View, Text, ScrollView, StyleSheet, Image, TouchableOpacity, Linking } from "react-native";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
Linking,
|
||||
} from "react-native";
|
||||
import { Switch } from "@rneui/themed";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
@@ -69,7 +77,16 @@ const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View style={[styles.accordionCard, { backgroundColor: colors.surface, borderColor: colors.borderSecondary }, enabled && styles.accordionCardEnabled]}>
|
||||
<View
|
||||
style={[
|
||||
styles.accordionCard,
|
||||
{
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.borderSecondary,
|
||||
},
|
||||
enabled && styles.accordionCardEnabled,
|
||||
]}
|
||||
>
|
||||
{/* ── ヘッダー行(常時表示) ── */}
|
||||
<View style={styles.accordionHeader}>
|
||||
{/* 左:ロゴ */}
|
||||
@@ -77,8 +94,14 @@ const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||
|
||||
{/* 中央:タイトル+タグライン */}
|
||||
<View style={styles.accordionTitles}>
|
||||
<Text style={[styles.accordionTitle, { color: colors.textPrimary }]}>{title}</Text>
|
||||
<Text style={[styles.accordionTagline, { color: colors.textTertiary }]}>{tagline}</Text>
|
||||
<Text style={[styles.accordionTitle, { color: colors.textPrimary }]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.accordionTagline, { color: colors.textTertiary }]}
|
||||
>
|
||||
{tagline}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 右:スイッチ */}
|
||||
@@ -92,19 +115,36 @@ const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||
|
||||
{/* スイッチ状態テキスト */}
|
||||
<View style={styles.accordionStatusRow}>
|
||||
<View style={[styles.statusDot, { backgroundColor: enabled ? accentColor : colors.textDisabled }]} />
|
||||
<Text style={[styles.statusText, { color: enabled ? accentColor : colors.textQuaternary }]}>
|
||||
{enabled ? "有効 — 編成データを取得します" : "無効 — データを取得しません"}
|
||||
<View
|
||||
style={[
|
||||
styles.statusDot,
|
||||
{ backgroundColor: enabled ? accentColor : colors.textDisabled },
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.statusText,
|
||||
{ color: enabled ? accentColor : colors.textQuaternary },
|
||||
]}
|
||||
>
|
||||
{enabled
|
||||
? "有効 — 編成データを取得します"
|
||||
: "無効 — データを取得しません"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* ── 展開トリガー ── */}
|
||||
<TouchableOpacity
|
||||
style={[styles.accordionToggleRow, { borderTopColor: colors.borderCard }]}
|
||||
style={[
|
||||
styles.accordionToggleRow,
|
||||
{ borderTopColor: colors.borderCard },
|
||||
]}
|
||||
onPress={() => setExpanded((v) => !v)}
|
||||
activeOpacity={0.6}
|
||||
>
|
||||
<Text style={[styles.accordionToggleLabel, { color: colors.textSecondary }]}>
|
||||
<Text
|
||||
style={[styles.accordionToggleLabel, { color: colors.textSecondary }]}
|
||||
>
|
||||
{expanded ? "詳細を閉じる" : (detailLabel ?? `${title} について`)}
|
||||
</Text>
|
||||
<MaterialCommunityIcons
|
||||
@@ -116,31 +156,69 @@ const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||
|
||||
{/* ── 展開コンテンツ ── */}
|
||||
{expanded && (
|
||||
<View style={[styles.accordionBody, { borderTopColor: colors.borderCard, backgroundColor: colors.backgroundTertiary }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.accordionBody,
|
||||
{
|
||||
borderTopColor: colors.borderCard,
|
||||
backgroundColor: colors.backgroundTertiary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* 説明文 */}
|
||||
<Text style={[styles.bodyDesc, { color: colors.textSecondary }]}>{description}</Text>
|
||||
<Text style={[styles.bodyDesc, { color: colors.textSecondary }]}>
|
||||
{description}
|
||||
</Text>
|
||||
|
||||
{/* 機能リスト */}
|
||||
<View style={[styles.bodyFeatures, { borderTopColor: colors.borderSecondary }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.bodyFeatures,
|
||||
{ borderTopColor: colors.borderSecondary },
|
||||
]}
|
||||
>
|
||||
{features.map((f) => (
|
||||
<View key={f.icon} style={styles.featureRow}>
|
||||
<View style={styles.featureIcon}>
|
||||
<MaterialCommunityIcons name={f.icon as any} size={14} color={colors.iconSecondary} />
|
||||
<MaterialCommunityIcons
|
||||
name={f.icon as any}
|
||||
size={14}
|
||||
color={colors.iconSecondary}
|
||||
/>
|
||||
</View>
|
||||
<Text style={[styles.featureLabel, { color: colors.textPrimary }]}>{f.label}</Text>
|
||||
<Text style={[styles.featureText, { color: colors.textSecondary }]}>{f.text}</Text>
|
||||
<Text
|
||||
style={[styles.featureLabel, { color: colors.textPrimary }]}
|
||||
>
|
||||
{f.label}
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.featureText, { color: colors.textSecondary }]}
|
||||
>
|
||||
{f.text}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* リンク */}
|
||||
<TouchableOpacity
|
||||
style={[styles.bodyLink, { borderTopColor: colors.borderSecondary }]}
|
||||
style={[
|
||||
styles.bodyLink,
|
||||
{ borderTopColor: colors.borderSecondary },
|
||||
]}
|
||||
onPress={() => Linking.openURL(linkUrl)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialCommunityIcons name="open-in-new" size={13} color={colors.iconSecondary} />
|
||||
<Text style={[styles.bodyLinkText, { color: colors.textSecondary }]}>{linkLabel}</Text>
|
||||
<MaterialCommunityIcons
|
||||
name="open-in-new"
|
||||
size={13}
|
||||
color={colors.iconSecondary}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.bodyLinkText, { color: colors.textSecondary }]}
|
||||
>
|
||||
{linkLabel}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
@@ -152,17 +230,45 @@ const DataSourceAccordionCard: React.FC<DataSourceAccordionCardProps> = ({
|
||||
/* 定数 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
const UNYOHUB_FEATURES: Feature[] = [
|
||||
{ icon: "calendar-today", label: "運用データ", text: "当日・過去数日から投稿があった運用の継続予測運用情報を表示" },
|
||||
{ icon: "map-outline", label: "対象エリア", text: "JR四国全線" },
|
||||
{ icon: "train", label: "対象運用", text: "JR四国管内営業列車及び貨物列車,定期回送列車に対応、臨時列車/突発運用は非対応" },
|
||||
{ icon: "pencil", label: "入力方式", text: "アプリ内連携システムにて当日の運用の投稿が可能" },
|
||||
{
|
||||
icon: "calendar-today",
|
||||
label: "運用データ",
|
||||
text: "当日・過去数日から投稿があった運用の継続予測運用情報を表示",
|
||||
},
|
||||
{ icon: "map-outline", label: "対象エリア", text: "JR四国全線" },
|
||||
{
|
||||
icon: "train",
|
||||
label: "対象運用",
|
||||
text: "JR四国管内営業列車及び貨物列車,定期回送列車に対応、臨時列車/突発運用は非対応",
|
||||
},
|
||||
{
|
||||
icon: "pencil",
|
||||
label: "入力方式",
|
||||
text: "アプリ内連携システムにて当日の運用の投稿が可能",
|
||||
},
|
||||
];
|
||||
|
||||
const ELESITE_FEATURES: Feature[] = [
|
||||
{ icon: "calendar-today", label: "運用データ", text: "当日報告のあった運用情報のみ表示" },
|
||||
{ icon: "map-outline", label: "対象エリア", text: "予讃線/瀬戸大橋線(直通している特急などの列番は含みます)" },
|
||||
{ icon: "train", label: "対象運用", text: "JR四国管内営業列車対応、臨時列車/突発運用は非対応" },
|
||||
{ icon: "pencil", label: "入力方式", text: "アプリ外リンク連携にて当日の運用の投稿が可能" },
|
||||
{
|
||||
icon: "calendar-today",
|
||||
label: "運用データ",
|
||||
text: "当日報告のあった運用情報のみ表示",
|
||||
},
|
||||
{
|
||||
icon: "map-outline",
|
||||
label: "対象エリア",
|
||||
text: "予讃線/瀬戸大橋線(直通している特急などの列番は含みます)",
|
||||
},
|
||||
{
|
||||
icon: "train",
|
||||
label: "対象運用",
|
||||
text: "JR四国管内営業列車対応、臨時列車/突発運用は非対応",
|
||||
},
|
||||
{
|
||||
icon: "pencil",
|
||||
label: "入力方式",
|
||||
text: "アプリ外リンク連携にて当日の運用の投稿が可能",
|
||||
},
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -170,17 +276,17 @@ const ELESITE_FEATURES: Feature[] = [
|
||||
/* ------------------------------------------------------------------ */
|
||||
export const DataSourceSettings = () => {
|
||||
const navigation = useNavigation();
|
||||
const { updatePermission, mockApiFeatureEnabled, setMockApiFeatureEnabled } = useTrainMenu();
|
||||
const { updatePermission, mockApiFeatureEnabled, setMockApiFeatureEnabled } =
|
||||
useTrainMenu();
|
||||
const { colors, fixed } = useThemeColors();
|
||||
const showDebugSelector = __DEV__ || updatePermission;
|
||||
const [useUnyohub, setUseUnyohub] = useState(false);
|
||||
const [useElesite, setUseElesite] = useState(false);
|
||||
const [jrDataSystemEnv, setJrDataSystemEnv] =
|
||||
useState<JrDataSystemEnvironmentKey>(DEFAULT_JR_DATA_SYSTEM_ENV);
|
||||
const [jrDataSystemTrack, setJrDataSystemTrack] =
|
||||
useState<JrDataSystemTrack>(
|
||||
getJrDataSystemTrack(DEFAULT_JR_DATA_SYSTEM_ENV),
|
||||
);
|
||||
const [jrDataSystemTrack, setJrDataSystemTrack] = useState<JrDataSystemTrack>(
|
||||
getJrDataSystemTrack(DEFAULT_JR_DATA_SYSTEM_ENV),
|
||||
);
|
||||
const [jrDataSystemUiVariant, setJrDataSystemUiVariant] =
|
||||
useState<JrDataSystemUiVariant>(
|
||||
getJrDataSystemUiVariant(DEFAULT_JR_DATA_SYSTEM_ENV),
|
||||
@@ -228,7 +334,8 @@ export const DataSourceSettings = () => {
|
||||
|
||||
const handleSelectJrDataSystemTrack = (value: JrDataSystemTrack) => {
|
||||
setJrDataSystemTrack(value);
|
||||
const normalizedVariant = value === "experimental" ? "release" : jrDataSystemUiVariant;
|
||||
const normalizedVariant =
|
||||
value === "experimental" ? "release" : jrDataSystemUiVariant;
|
||||
setJrDataSystemUiVariant(normalizedVariant);
|
||||
const env = resolveJrDataSystemEnvironment(value, normalizedVariant);
|
||||
applyJrDataSystemEnv(env);
|
||||
@@ -250,55 +357,100 @@ export const DataSourceSettings = () => {
|
||||
position: "left",
|
||||
}}
|
||||
/>
|
||||
<ScrollView style={[styles.content, { backgroundColor: colors.backgroundSecondary }]} contentContainerStyle={styles.contentInner}>
|
||||
<Text style={[styles.sectionTitle, { color: colors.textTertiary }]}>外部データソース</Text>
|
||||
<ScrollView
|
||||
style={[
|
||||
styles.content,
|
||||
{ backgroundColor: colors.backgroundSecondary },
|
||||
]}
|
||||
contentContainerStyle={styles.contentInner}
|
||||
>
|
||||
<Text style={[styles.sectionTitle, { color: colors.textTertiary }]}>
|
||||
外部データソース
|
||||
</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={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データがある列車では地図上にアイコンでマークが表示され、列車情報画面の編成表示も更新されます。"
|
||||
}
|
||||
features={ELESITE_FEATURES}
|
||||
linkLabel="elesite-next.com を開く"
|
||||
linkUrl="https://www.elesite-next.com/"
|
||||
/>
|
||||
<DataSourceAccordionCard
|
||||
logo={ELESITE_LOGO_PNG}
|
||||
accentColor="#44bb44"
|
||||
title="えれサイト"
|
||||
tagline="コミュニティによる列車運用情報サービス"
|
||||
enabled={useElesite}
|
||||
onToggle={handleToggleElesite}
|
||||
description={
|
||||
"えれサイトは、鉄道の運用情報を利用者同士で共有するサービスです。皆様からの投稿をもとに、列車のリアルタイムな動きを反映しています。JR四国の特急・普通列車をはじめ、現在は全国の路線に対応しています。\n\nデータがある列車では地図上にアイコンでマークが表示され、列車情報画面の編成表示も更新されます。"
|
||||
}
|
||||
features={ELESITE_FEATURES}
|
||||
linkLabel="elesite-next.com を開く"
|
||||
linkUrl="https://www.elesite-next.com/"
|
||||
/>
|
||||
|
||||
<View style={[styles.infoSection, { backgroundColor: colors.backgroundTertiary }]}>
|
||||
<Text style={[styles.infoText, { color: colors.textCaution }]}>
|
||||
外部のコミュニティデータソースとの連携を管理します。
|
||||
{"\n\n"}
|
||||
データの正確性は保証されません。また、これらの連携情報を利用する時点でそれぞれのサイトの利用規約に同意したものとします。{"\n\n"}外部ソースはJR四国非公式アプリが管理していないデータであるため、お問い合わせは各サービスの窓口までお願いいたします。
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
styles.infoSection,
|
||||
{ backgroundColor: colors.backgroundTertiary },
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.infoText, { color: colors.textCaution }]}>
|
||||
外部のコミュニティデータソースとの連携を管理します。
|
||||
{"\n\n"}
|
||||
データの正確性は保証されません。また、これらの連携情報を利用する時点でそれぞれのサイトの利用規約に同意したものとします。
|
||||
{"\n\n"}
|
||||
外部ソースはJR四国非公式アプリが管理していないデータであるため、お問い合わせは各サービスの窓口までお願いいたします。
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{showDebugSelector && (
|
||||
<View style={[styles.debugSection, { backgroundColor: colors.surface, borderColor: colors.borderSecondary }]}>
|
||||
<Text style={[styles.debugTitle, { color: colors.textPrimary }]}>デバッグ: モックAPI検証</Text>
|
||||
<Text style={[styles.debugDescription, { color: colors.textSecondary }]}>
|
||||
走行位置画面にモックAPIスイッチを表示し、公式サイトの代わりにサンプルデータを流し込みます。
|
||||
{showDebugSelector && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.debugSection,
|
||||
{
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.borderSecondary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.debugTitle, { color: colors.textPrimary }]}>
|
||||
デバッグ: モックAPI検証
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginTop: 8 }}>
|
||||
<Text style={[styles.debugCurrentText, { color: colors.textPrimary, fontSize: 14 }]}>モックAPI検証機能</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugDescription,
|
||||
{ color: colors.textSecondary },
|
||||
]}
|
||||
>
|
||||
公式サイトの代わりにサンプルデータを流し込みます。
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugCurrentText,
|
||||
{ color: colors.textPrimary, fontSize: 14 },
|
||||
]}
|
||||
>
|
||||
モックAPI検証機能
|
||||
</Text>
|
||||
<Switch
|
||||
value={mockApiFeatureEnabled}
|
||||
onValueChange={setMockApiFeatureEnabled}
|
||||
@@ -306,19 +458,48 @@ export const DataSourceSettings = () => {
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[styles.debugSection, { backgroundColor: colors.surface, borderColor: colors.borderSecondary }]}>
|
||||
<Text style={[styles.debugTitle, { color: colors.textPrimary }]}>投稿システム接続先</Text>
|
||||
<Text style={[styles.debugDescription, { color: colors.textSecondary }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.debugSection,
|
||||
{
|
||||
backgroundColor: colors.surface,
|
||||
borderColor: colors.borderSecondary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.debugTitle, { color: colors.textPrimary }]}>
|
||||
投稿システム接続先
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugDescription,
|
||||
{ color: colors.textSecondary },
|
||||
]}
|
||||
>
|
||||
本番運用と実験場、および本番のリリース版/ベータ版を切り替えます。
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.debugCurrentText, { color: colors.textTertiary }]}>系統</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugCurrentText,
|
||||
{ color: colors.textTertiary },
|
||||
]}
|
||||
>
|
||||
系統
|
||||
</Text>
|
||||
<View style={styles.debugOptionRow}>
|
||||
{[
|
||||
{ key: "production" as const, label: "本番", caption: "一般公開向け" },
|
||||
{ key: "experimental" as const, label: "実験", caption: "毎日リセット" },
|
||||
{
|
||||
key: "production" as const,
|
||||
label: "本番",
|
||||
caption: "一般公開向け",
|
||||
},
|
||||
{
|
||||
key: "experimental" as const,
|
||||
label: "実験",
|
||||
caption: "毎日リセット",
|
||||
},
|
||||
].map((option) => {
|
||||
const selected = jrDataSystemTrack === option.key;
|
||||
return (
|
||||
@@ -341,7 +522,11 @@ export const DataSourceSettings = () => {
|
||||
<Text
|
||||
style={[
|
||||
styles.debugOptionTitle,
|
||||
{ color: selected ? fixed.textOnPrimary : colors.textPrimary },
|
||||
{
|
||||
color: selected
|
||||
? fixed.textOnPrimary
|
||||
: colors.textPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
@@ -349,7 +534,11 @@ export const DataSourceSettings = () => {
|
||||
<Text
|
||||
style={[
|
||||
styles.debugOptionCaption,
|
||||
{ color: selected ? fixed.textOnPrimary : colors.textTertiary },
|
||||
{
|
||||
color: selected
|
||||
? fixed.textOnPrimary
|
||||
: colors.textTertiary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{option.caption}
|
||||
@@ -359,11 +548,26 @@ export const DataSourceSettings = () => {
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Text style={[styles.debugCurrentText, { color: colors.textTertiary }]}>UIバージョン(本番のみ)</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugCurrentText,
|
||||
{ color: colors.textTertiary },
|
||||
]}
|
||||
>
|
||||
UIバージョン(本番のみ)
|
||||
</Text>
|
||||
<View style={styles.debugOptionRow}>
|
||||
{[
|
||||
{ key: "release" as const, label: "リリース", caption: "安定版" },
|
||||
{ key: "beta" as const, label: "ベータ", caption: "夜間ビルド" },
|
||||
{
|
||||
key: "release" as const,
|
||||
label: "リリース",
|
||||
caption: "安定版",
|
||||
},
|
||||
{
|
||||
key: "beta" as const,
|
||||
label: "ベータ",
|
||||
caption: "夜間ビルド",
|
||||
},
|
||||
].map((option) => {
|
||||
const selected = jrDataSystemUiVariant === option.key;
|
||||
const disabled = jrDataSystemTrack === "experimental";
|
||||
@@ -391,7 +595,11 @@ export const DataSourceSettings = () => {
|
||||
<Text
|
||||
style={[
|
||||
styles.debugOptionTitle,
|
||||
{ color: selected ? fixed.textOnPrimary : colors.textPrimary },
|
||||
{
|
||||
color: selected
|
||||
? fixed.textOnPrimary
|
||||
: colors.textPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
@@ -399,7 +607,11 @@ export const DataSourceSettings = () => {
|
||||
<Text
|
||||
style={[
|
||||
styles.debugOptionCaption,
|
||||
{ color: selected ? fixed.textOnPrimary : colors.textTertiary },
|
||||
{
|
||||
color: selected
|
||||
? fixed.textOnPrimary
|
||||
: colors.textTertiary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{option.caption}
|
||||
@@ -408,9 +620,23 @@ export const DataSourceSettings = () => {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Text style={[styles.debugCurrentText, { color: colors.textTertiary }]}>現在の接続先: {JR_DATA_SYSTEM_ENV_OPTIONS.find((option) => option.key === jrDataSystemEnv)?.baseUrl}</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.debugCurrentText,
|
||||
{ color: colors.textTertiary },
|
||||
]}
|
||||
>
|
||||
現在の接続先:{" "}
|
||||
{
|
||||
JR_DATA_SYSTEM_ENV_OPTIONS.find(
|
||||
(option) => option.key === jrDataSystemEnv,
|
||||
)?.baseUrl
|
||||
}
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,8 +13,9 @@ import WebView from "react-native-webview";
|
||||
* pingHandlers を使う場合は onMessage を上書きせず spread すること。
|
||||
* ping による白画面検知を有効にするには pingEnabled: true を渡す。
|
||||
*/
|
||||
export function useWebViewRemount(options?: { pingEnabled?: boolean }) {
|
||||
export function useWebViewRemount(options?: { pingEnabled?: boolean; backgroundThresholdMs?: number }) {
|
||||
const pingEnabled = options?.pingEnabled ?? false;
|
||||
const backgroundThresholdMs = options?.backgroundThresholdMs ?? 300_000; // デフォルト5分
|
||||
const [remountKey, setRemountKey] = useState(0);
|
||||
const backgroundedAt = useRef<number | null>(null);
|
||||
const webViewRef = useRef<WebView>(null);
|
||||
@@ -29,7 +30,7 @@ export function useWebViewRemount(options?: { pingEnabled?: boolean }) {
|
||||
setRemountKey((k) => k + 1);
|
||||
}, []);
|
||||
|
||||
// バックグラウンド10秒超から復帰したら再マウント
|
||||
// バックグラウンドから復帰したら再マウント(デフォルト5分超)
|
||||
useEffect(() => {
|
||||
const onAppStateChange = (nextState: AppStateStatus) => {
|
||||
if (nextState.match(/inactive|background/)) {
|
||||
@@ -37,14 +38,14 @@ export function useWebViewRemount(options?: { pingEnabled?: boolean }) {
|
||||
} else if (nextState === "active" && backgroundedAt.current !== null) {
|
||||
const elapsed = Date.now() - backgroundedAt.current;
|
||||
backgroundedAt.current = null;
|
||||
if (elapsed > 10_000) {
|
||||
if (elapsed > backgroundThresholdMs) {
|
||||
remount();
|
||||
}
|
||||
}
|
||||
};
|
||||
const subscription = AppState.addEventListener("change", onAppStateChange);
|
||||
return () => subscription.remove();
|
||||
}, [remount]);
|
||||
}, [remount, backgroundThresholdMs]);
|
||||
|
||||
// ping watchdog: 5秒ごとに生存確認と白画面検知を行う
|
||||
// - ローディング中(isLoadingRef=true)でも45秒超なら remount(レンダラー死亡でonLoadEndが来ないケース)
|
||||
|
||||
@@ -16,20 +16,19 @@ import { useThemeColors } from "@/lib/theme";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useWebViewRemount } from "@/lib/useWebViewRemount";
|
||||
export default function tndView() {
|
||||
const { remountKey, remount, processHandlers, pingHandlers, webViewRef } = useWebViewRemount({ pingEnabled: true });
|
||||
const { remountKey, remount, processHandlers, pingHandlers, webViewRef } = useWebViewRemount({ pingEnabled: true, backgroundThresholdMs: 60_000 }); // 1分超でAppState remount
|
||||
const { navigate, addListener, isFocused } = useNavigation();
|
||||
const { fixed } = useThemeColors();
|
||||
const { top } = useSafeAreaInsets();
|
||||
|
||||
// タブ切り替え時の自動復帰: バックグラウンドから戻ったとき remount
|
||||
// タブにフォーカスが来たとき、バックグラウンドから戻ってきた場合 remount
|
||||
const bgAtRef = useRef<number | null>(null);
|
||||
useEffect(() => {
|
||||
const sub = AppState.addEventListener("change", (state) => {
|
||||
if (state.match(/inactive|background/)) {
|
||||
bgAtRef.current = Date.now();
|
||||
} else if (state === "active" && bgAtRef.current !== null) {
|
||||
// 10秒超はuseWebViewRemount側が処理するのでここではクリアのみ
|
||||
if (Date.now() - bgAtRef.current > 10_000) bgAtRef.current = null;
|
||||
} else if (state === "active") {
|
||||
// useWebViewRemount側(1分超)とは独立: ここではフォーカス時判定用に保持するだけ
|
||||
}
|
||||
});
|
||||
return () => sub.remove();
|
||||
|
||||
Reference in New Issue
Block a user