ナビゲーションロジックを改善し、stackAwareNavigate関数を導入して遷移時のスタック管理を強化。プライバシーポリシーと設計メモを追加。

This commit is contained in:
harukin-expo-dev-env
2026-04-08 02:54:14 +00:00
parent 6b46c7150c
commit a54ef7ca13
8 changed files with 328 additions and 21 deletions

22
App.tsx
View File

@@ -25,7 +25,7 @@ import { buildProvidersTree } from "./lib/providerTreeProvider";
import { StationListProvider } from "./stateBox/useStationList";
import { NotificationProvider } from "./stateBox/useNotifications";
import { UserPositionProvider } from "./stateBox/useUserPosition";
import { rootNavigationRef } from "./lib/rootNavigation";
import { rootNavigationRef, stackAwareNavigate } from "./lib/rootNavigation";
import { AppThemeProvider } from "./lib/theme";
import StatusbarDetect from "./StatusbarDetect";
@@ -54,12 +54,12 @@ export default function App() {
return;
}
rootNavigationRef.navigate("topMenu", {
stackAwareNavigate("topMenu", {
screen: "setting",
params: {
screen: "FelicaHistoryPage",
},
} as any);
});
};
const navigateWhenReady = (
@@ -87,26 +87,30 @@ export default function App() {
navigateWhenReady(() => openFelicaPage(), url, retryCount);
} else if (normalized.includes("open/traininfo")) {
navigateWhenReady(() => {
rootNavigationRef.navigate("topMenu", { screen: "menu" } as any);
stackAwareNavigate("topMenu", { screen: "menu" });
setTimeout(() => {
SheetManager.show("JRSTraInfo");
}, 450);
}, url, retryCount);
} else if (normalized.includes("open/operation")) {
navigateWhenReady(() => {
rootNavigationRef.navigate("information" as any);
stackAwareNavigate("information");
}, url, retryCount);
} else if (normalized.includes("open/settings")) {
navigateWhenReady(() => {
rootNavigationRef.navigate("topMenu", {
stackAwareNavigate("topMenu", {
screen: "setting",
} as any);
});
}, url, retryCount);
} else if (normalized.includes("open/topmenu")) {
navigateWhenReady(() => {
rootNavigationRef.navigate("topMenu", {
stackAwareNavigate("topMenu", {
screen: "menu",
} as any);
});
}, url, retryCount);
} else if (normalized.includes("positions/apps")) {
navigateWhenReady(() => {
stackAwareNavigate("positions");
}, url, retryCount);
}
};

View File

@@ -68,9 +68,7 @@ export function AppContainer() {
config: {
screens: {
positions: {
screens: {
Apps: "positions/apps",
},
screens: {},
},
topMenu: {
screens: {

View File

@@ -84,7 +84,14 @@ export function MenuPage() {
setMapFullHeight(MapFullHeight);
}, [height, tabBarHeight, width]);
useEffect(() => {
const unsubscribe = addListener("tabPress", (e) => {
const unsubscribe = addListener("tabPress", (e: any) => {
if (navigation.isFocused() && stackNavRef.current) {
if (stackNavRef.current.getState()?.index > 0) {
e.preventDefault();
stackNavRef.current.goBack();
return;
}
}
scrollRef.current?.scrollTo({
y: mapHeightRef.current - verticalScale(80),
animated: true,
@@ -106,10 +113,16 @@ export function MenuPage() {
return unsubscribe;
}, [navigation]);
const stackNavRef = useRef<any>(null);
return (
<Stack.Navigator
id={null}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
screenListeners={({ navigation: stackNav }) => {
stackNavRef.current = stackNav;
return {};
}}
>
<Stack.Screen
name="menu"

19
Top.tsx
View File

@@ -16,6 +16,7 @@ import { news } from "./config/newsUpdate";
import { Linking, Platform } from "react-native";
import GeneralWebView from "./GeneralWebView";
import { StationDiagramView } from "@/components/StationDiagram/StationDiagramView";
import { positionsStackNavRef } from "./lib/rootNavigation";
const Stack = createStackNavigator();
export const Top = () => {
const { webview } = useCurrentTrain();
@@ -38,14 +39,22 @@ export const Top = () => {
return unsubscribe;
}, []);
const goToTrainMenu = useCallback(() => {
const stackNavRef = positionsStackNavRef;
const goToTrainMenu = useCallback((e: any) => {
if (Platform.OS === "web") {
Linking.openURL("https://train.jr-shikoku.co.jp/");
setTimeout(() => navigate("topMenu", { screen: "menu" }), 100);
return;
}
if (!isFocused()) navigate("positions", { screen: "Apps" });
else if (mapSwitchRef.current == "true")
if (!isFocused()) return;
const stackNav = stackNavRef.current;
if (stackNav && stackNav.getState()?.index > 0) {
e.preventDefault();
stackNav.goBack();
return;
}
if (mapSwitchRef.current == "true")
navigate("positions", { screen: "trainMenu" });
else webview.current?.injectJavaScript(`AccordionClassEvent()`);
return;
@@ -60,6 +69,10 @@ export const Top = () => {
<Stack.Navigator
id={null}
screenOptions={{ cardStyle: { backgroundColor: bgColor } }}
screenListeners={({ navigation: stackNav }) => {
stackNavRef.current = stackNav;
return {};
}}
>
<Stack.Screen
name="Apps"

View File

@@ -0,0 +1,84 @@
# プライバシーポリシー 将来追加予定セクション
このファイルは、今後の機能実装時にプライバシーポリシーへ追加する内容の設計メモです。
各機能のリリース時に privacy-policy.md へ統合してください。
---
## アカウント機能OAuthログインリリース時に追加
### 1.1 アカウント情報(新規セクション)
本アプリでは、ソーシャルログインOAuthによるアカウント機能を提供します。
- ログイン時に、利用する認証プロバイダGoogle、Apple等から以下の情報を取得します。
- 認証プロバイダが発行するユーザー識別子ID
- 表示名(認証プロバイダが提供する場合)
- メールアドレス(認証プロバイダが提供する場合)
- これらの情報はアカウント管理および設定同期のために開発者のサーバーに保存します。
- パスワードを開発者側で保持することはありません。認証は各プロバイダに委任されます。
- アカウントの作成は任意であり、アカウントなしでも本アプリの基本機能は利用できます。
### 1.3 ユーザー設定の同期(新規セクション)
- アカウントにログインしている場合、以下の設定情報をサーバーに同期し、複数端末間で共有できます。
- お気に入り駅
- 表示設定(テーマ、レイアウト等)
- 通知設定
- カスタマイズ設定(アプリアイコン選択等)
- 同期はアカウントにログインしている場合にのみ行われます。未ログイン時はすべて端末内にのみ保存されます。
### 3.1 第三者サービス表に追加
| OAuth認証プロバイダGoogle、Apple等 | ソーシャルログイン | 各プロバイダ |
### 6. データの保存期間に追加
- **アカウント情報・同期設定:** アカウントが削除されるまで保持します。
- プッシュ通知トークンの記述に追記: 「またはアカウント削除時に削除されます。」
### 7. データの削除に追加
#### アカウントの削除
- アプリ内の設定画面からアカウントを削除できます。アカウント削除時に、サーバーに保存されたアカウント情報、同期設定データ、およびプッシュ通知トークンを削除します。
### 9. 児童のプライバシーに追記
- 13歳未満の児童がアカウント作成等を通じて個人情報を提供したことが判明した場合、速やかに当該アカウントおよび関連情報を削除します。
---
## FeliCa OSSデータベース機能リリース時に追加
### 1.4 ユーザーが任意で提供する情報FeliCaオープンデータ新規セクション
本アプリでは、交通系ICカードの駅コード・改札コードに関するオープンデータベースの構築に参加する機能を提供します。
#### 提供されるデータの範囲
- **提供対象:** 駅コード、改札コード、およびユーザーが入力する実績説明(駅名・改札名の対応情報等)
- **提供対象外:** IDm製造ID、残高、個人の乗降履歴、その他個人を特定しうる情報
#### データの公開
- 提供されたデータは、オープンソースのデータベースとして**一般に公開**されます。
- 公開されるデータには提供者個人を特定する情報は含まれません。
- 一度公開されたデータは、オープンソースとして第三者が複製・再配布する可能性があるため、**完全な削除を保証することはできません**。
#### 同意と任意性
- データの提供は完全に任意であり、提供しなくても本アプリの機能に制限はありません。
- データの提供時に、提供内容と公開範囲について個別に同意を求めます。
### 3.2 FeliCaオープンデータの公開新規セクション
ユーザーが任意で提供したFeliCa駅コード・改札コードのデータは、オープンソースデータベースとして一般に公開されます。公開データに個人を特定する情報は含まれません。
### 6. データの保存期間に追加
- **FeliCaオープンデータ:** オープンソースとして無期限に公開されます。
### 7. データの削除に追記
- アカウント削除後も、FeliCaオープンデータベースに提供済みのデータは、オープンソースデータとして公開が継続されます。
### 1.2 ICカード個人データの記述に追記
- ICカードのデータ提供機能後述の1.4)とは明確に区別されます。

151
docs/privacy-policy.md Normal file
View File

@@ -0,0 +1,151 @@
# 『JR四国非公式アプリ』プライバシーポリシー
最終更新日: 2026年4月3日
本プライバシーポリシー以下「本ポリシー」は、harukin以下「開発者」が提供する「JR四国非公式アプリ」以下「本アプリ」における、ユーザー情報の取り扱いについて定めるものです。
本アプリはJR四国四国旅客鉄道株式会社とは一切関係のない非公式アプリです。
---
## 1. 収集する情報
### 1.1 端末から取得し、端末内のみで利用する情報
以下の情報は端末内での処理にのみ使用し、外部サーバーへの送信は行いません。
#### 位置情報
- ユーザーの現在地を取得し、最寄り駅の情報表示や地図上での現在地表示に利用します。
- 位置情報はアプリがフォアグラウンドで動作している間のみ取得し、アプリを終了すると取得を停止します。
- 位置情報の利用は任意ですが、許可しない場合は最寄り駅の自動検出などの一部機能が制限されます。
#### ICカードデータNFC/FeliCa
- NFC対応端末において、交通系ICカードのIDm製造ID、残高、システムコード、利用履歴を読み取ります。
- 読み取った情報は端末内AsyncStorageにのみ保存され、ウィジェット等での残高表示に使用します。
- IDm、残高、個人の利用履歴等のデータは外部サーバーへ送信しません。
#### ユーザー設定・お気に入り
- お気に入り駅、表示設定テーマ・レイアウト等、通知設定、カスタマイズ設定などのユーザー設定は、すべて端末内AsyncStorageにのみ保存されます。
### 1.2 外部サーバーに送信する情報
以下の情報は、機能の提供に必要な範囲で開発者が運営するサーバーに送信されます。
#### プッシュ通知トークンExpo Push Token
- プッシュ通知の配信および一部機能の権限確認のために、端末固有のプッシュ通知トークンを開発者のサーバーに送信します。
- このトークンはユーザーの識別にも使用されますが、氏名・メールアドレス等の個人を直接特定する情報とは紐づけません。
#### 通知設定情報
- プッシュ通知機能を利用する場合、通知カテゴリの設定内容遅延情報EX、運行情報、怪レい列車BOTの各ON/OFFをプッシュ通知トークンとともにサーバーに送信・保存します。
---
## 2. 情報の利用目的
収集した情報は以下の目的でのみ利用します。
- 最寄り駅の検出および地図上での現在地表示
- ICカード残高・利用履歴の端末内表示
- プッシュ通知の配信(列車遅延情報、運行情報等)
- 一部機能における権限確認
- アプリの機能改善および不具合対応
---
## 3. 第三者への情報提供
開発者は、収集した情報を第三者に販売または貸与することはありません。
ただし、本アプリは以下の第三者サービスを利用しており、各サービスの利用規約およびプライバシーポリシーに基づき、当該サービス提供者が情報を取得する場合があります。
| サービス | 用途 | 提供者 |
|---------|------|--------|
| Google Maps SDK | 地図表示・駅マーカー表示 | Google LLC |
| Firebase Cloud Messaging (FCM) | プッシュ通知基盤 | Google LLC |
| Expo Push Notifications | プッシュ通知配信 | Expo |
| Expo Updates | アプリのOTAアップデート配信 | Expo |
各サービスのプライバシーポリシーについては、以下をご参照ください。
- Google: https://policies.google.com/privacy
- Expo: https://expo.dev/privacy
---
## 4. WebViewを通じた第三者サイトへのアクセス
本アプリでは、以下のWebサイトをWebViewにより表示します。これらのサイトにおけるデータの取り扱いは各サイトのプライバシーポリシーに従います。
- JR四国 列車走行位置https://train.jr-shikoku.co.jp/
- JR四国 運行情報https://www.jr-shikoku.co.jp/info/
---
## 5. 第三者データソースの利用
本アプリでは、設定画面から任意で以下の第三者データソースとの連携を有効にできます。
- 鉄道運用Hubunyohub
- えれサイトelesite
これらのデータソースから取得したデータは表示目的でのみ使用し、ユーザーの情報をこれらのサービスに送信することはありません。連携機能を利用する場合は、各サービスの利用規約に同意したものとみなします。
一部の第三者データソース鉄道運用Hub等は独自のログイン機能を提供しています。ユーザーがWebView内でログインした場合、そのセッション情報Cookie等はアプリ内のWebViewストレージに保持されます。開発者がこれらの認証情報にアクセスすることはありません。セッション情報の取り扱いは各サービスのプライバシーポリシーに従います。
---
## 6. データの保存期間
### サーバー側データ
- **プッシュ通知トークン・通知設定:** ユーザーが明示的に削除を要求するまで、またはサービス終了まで保持します。
### 端末内データ
- ユーザー設定、お気に入り、ICカードデータ、キャッシュデータ等は、ユーザーがアプリをアンインストールするか、端末内のデータを消去するまで保持されます。
---
## 7. データの削除
### 端末内データの削除
- アプリのアンインストール、または端末の設定からアプリのデータ消去を行うことで、端末内に保存されたすべてのデータを削除できます。
### サーバー側データの削除
- サーバーに保存されたプッシュ通知トークンおよび通知設定の削除を希望する場合は、後述の問い合わせ先までご連絡ください。
---
## 8. セキュリティ
開発者は、収集した情報の漏洩、紛失、改ざんを防止するために合理的な技術的措置を講じます。ただし、インターネットを通じたデータ送信の安全性を完全に保証するものではありません。
---
## 9. 児童のプライバシー
本アプリは特定の年齢層を対象としたものではなく、13歳未満の児童から意図的に個人情報を収集することはありません。
---
## 10. アナリティクス・広告
本アプリはアクセス解析ツール(アナリティクス)および広告配信サービスを使用していません。
---
## 11. 本ポリシーの変更
開発者は、本ポリシーを変更する場合があります。重要な変更を行う場合は、アプリ内のお知らせまたは開発者のWebサイトにて通知します。変更後も本アプリを継続利用した場合、変更後のポリシーに同意したものとみなします。
---
## 12. お問い合わせ先
本ポリシーに関するお問い合わせは、以下の窓口までご連絡ください。
- **開発者:** harukinXprocess
- **Twitter/X:** [@xprocess_main](https://twitter.com/xprocess_main)
- **マシュマロ(匿名質問箱):** https://marshmallow-qa.com/pag3sl0ju3g1jm7
- **Webサイト:** https://haruk.in
---
© 2022 - 2026 harukin

View File

@@ -1,3 +1,47 @@
import { createNavigationContainerRef } from "@react-navigation/native";
import { createNavigationContainerRef, StackActions } from "@react-navigation/native";
export const rootNavigationRef = createNavigationContainerRef<any>();
/** positions タブの Stack.Navigator navigation を登録するグローバルref */
export const positionsStackNavRef: { current: any } = { current: null };
/**
* 遷移先タブのネストスタックを一度 popToTop してからナビゲートする。
* ウィジェットや外部リンクからの遷移時に、既存の開いている画面を閉じてから目的の画面へ移動するために使用する。
*/
export function stackAwareNavigate(tabName: string, params?: any) {
if (!rootNavigationRef.isReady()) return;
const doNavigate = () => {
if (params !== undefined) {
rootNavigationRef.navigate(tabName, params);
} else {
rootNavigationRef.navigate(tabName as any);
}
};
// positions タブは直接 stackNavRef を使って確実に popToTop する
if (tabName === "positions" && positionsStackNavRef.current) {
const stackNav = positionsStackNavRef.current;
if ((stackNav.getState()?.index ?? 0) > 0) {
stackNav.popToTop();
setTimeout(doNavigate, 350);
} else {
doNavigate();
}
return;
}
const state = rootNavigationRef.getState();
const tabRoute = state?.routes?.find((r: any) => r.name === tabName);
if (tabRoute?.state && (tabRoute.state.index ?? 0) > 0) {
rootNavigationRef.dispatch({
...StackActions.popToTop(),
target: tabRoute.state.key,
});
// popToTop のアニメーション完了後に navigate を実行
setTimeout(doNavigate, 350);
} else {
doNavigate();
}
}

View File

@@ -11,7 +11,7 @@ import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import Constants from "expo-constants";
import { logger } from "@/utils/logger";
import { rootNavigationRef } from "@/lib/rootNavigation";
import { rootNavigationRef, stackAwareNavigate } from "@/lib/rootNavigation";
import { SheetManager } from "react-native-actions-sheet";
import { AS } from "@/storageControl";
import { STORAGE_KEYS } from "@/constants";
@@ -220,16 +220,16 @@ export const NotificationProvider: FC<Props> = ({ children }) => {
AS.setItem(STORAGE_KEYS.LAST_HANDLED_NOTIFICATION, requestId).catch(() => {});
switch (action) {
case "delay-ex":
rootNavigationRef.navigate("topMenu", { screen: "menu" });
stackAwareNavigate("topMenu", { screen: "menu" });
setTimeout(() => {
SheetManager.show("JRSTraInfo");
}, 450);
break;
case "strange-train":
rootNavigationRef.navigate("positions", { screen: "Apps" });
stackAwareNavigate("positions");
break;
case "information":
rootNavigationRef.navigate("information");
stackAwareNavigate("information");
break;
default:
break;