# Android ウィジェット機能 概要 ## ウィジェット一覧(app.json 登録済み: 4種) | 名前 | ラベル | 概要 | |---|---|---| | `JR_shikoku_train_info` | 列車遅延速報EX | GAS API から遅延データ取得→表示 | | `JR_shikoku_train_strange` | 怪レい列車 | app.json に定義あるが専用レンダリング分岐なし | | `JR_shikoku_info` | 運行情報 | app.json に定義あり。AsyncStorage の `widgetType/{id}` で `Info_Widget` に手動切替可能 | | `JR_shikoku_apps_shortcut` | クイックアクセス | FeliCa IC カード残高表示 + タップでスキャン画面へディープリンク | 全ウィジェット共通: `updatePeriodMillis: 1800000`(30分)、`resizeMode: "horizontal|vertical"` --- ## ファイル構成 | ファイル | 役割 | |---|---| | `components/AndroidWidget/TraInfoEXWidget.tsx` | 遅延速報EXウィジェットUI + データ取得 | | `components/AndroidWidget/InfoWidget.tsx` | 運行情報ウィジェットUI + データ取得 | | `components/AndroidWidget/FelicaQuickAccessWidget.tsx` | クイックアクセスウィジェットUI + 残高データ取得 | | `components/AndroidWidget/widget-task-handler.tsx` | ウィジェットイベントハンドラ(全ウィジェット共通) | | `components/AndroidWidget/HelloWidgetPreviewScreen.tsx` | 死コード(`HelloWidget` が存在しない) | | `components/Settings/WidgetSettings.tsx` | ウィジェット設定画面(ウィジェットIDごとに表示タイプ切替) | | `index.ts` | `registerWidgetTaskHandler()` でハンドラ登録(Android のみ) | | `app.json` | ウィジェットプラグイン設定 + intentFilters | | `constants/storage.ts` | `FELICA_LAST_SNAPSHOT` キー定義 | | `lib/rootNavigation.ts` | グローバルナビゲーション ref(`createNavigationContainerRef`) | --- ## ディープリンク設定 ### intentFilters(app.json) ```json [ { "action": "VIEW", "data": [{"scheme": "jrshikoku"}], "category": ["BROWSABLE", "DEFAULT"] }, { "action": "VIEW", "data": [{"scheme": "jrshikoku", "host": "open", "pathPrefix": "/felica"}], "category": ["BROWSABLE", "DEFAULT"] } ] ``` ### Linking config(Apps.tsx) ```typescript const linking = { prefixes: ["jrshikoku://"], config: { screens: { positions: { screens: { Apps: "positions/apps" } }, topMenu: { screens: { menu: "topMenu/menu", setting: { screens: { settingTopPage: "topMenu/setting", FelicaHistoryPage: "topMenu/setting/FelicaHistoryPage", }, }, }, }, information: "information", }, }, }; ``` --- ## ウィジェットタップ → Felica スキャン画面 フロー ``` FelicaQuickAccessWidget タップ → clickAction="OPEN_URI", uri="jrshikoku://open/felica" → Android OS が intentFilter マッチでアプリ起動 → App.tsx: Linking.getInitialURL() or addEventListener("url") → routeFromUrl(url): "open/felica" を検知 → openFelicaPage(): rootNavigationRef.navigate("topMenu" > "setting" > "FelicaHistoryPage") ※ nav 未 ready なら 250ms × 最大8回 retry → FelicaHistoryPage で NFC スキャン画面表示 → scan() 成功後: - UI に結果表示 - AS.setItem(FELICA_LAST_SNAPSHOT, { balance, idm, systemCode, scannedAt }) - requestWidgetUpdate("JR_shikoku_apps_shortcut") でウィジェット即時更新 ``` --- ## widget-task-handler イベントディスパッチ ``` widgetAction: WIDGET_ADDED / WIDGET_UPDATE / WIDGET_CLICK / WIDGET_RESIZED → widgetName === "JR_shikoku_apps_shortcut" → getFelicaQuickAccessData() → FelicaQuickAccessWidget 描画 → それ以外 → AS.getItem(`widgetType/${widgetId}`) でユーザー設定判定 - "Info_Widget" → getInfoString() → InfoWidget - "JR_shikoku_train_info" → getDelayData() → TraInfoEXWidget (デフォルト) WIDGET_DELETED → AS.removeItem(`widgetType/${widgetId}`) ``` --- ## 残高スナップショット保存箇所 | ファイル | トリガー | ウィジェット更新 | |---|---|---| | `FelicaHistoryPage.tsx` (L159) | `handleScan()` 成功 | ✅ `requestWidgetUpdate` あり | | `settings.tsx` (L63) | `testNFC()` 成功 | ⚠️ なし(次回定期更新まで反映されない) | 保存形式: `{ balance, idm, systemCode, scannedAt }` → AsyncStorage キー `felicaLastSnapshot` --- ## 通知タップ → ナビゲーション(useNotifications.tsx) | 判定キー | アクション | ナビゲーション先 | |---|---|---| | `遅延速報ex`, `trainfoex`, `tra_info_ex` | `delay-ex` | topMenu/menu → ActionSheet `JRSTraInfo` | | `怪レい列車bot`, `strange_train` | `strange-train` | positions/Apps | | `運行情報`, `information` | `information` | information タブ | チャンネルID / categoryIdentifier / data.category を優先判定、フォールバックで通知テキストの正規化マッチング。 --- ## ネイティブモジュール(expo-felica-reader) - エクスポート: `scan()` のみ - 戻り値: `FelicaCardInfo { idm, balance, systemCode, history[] }` - Android: `NfcF` (FeliCa) リーダーモード、サービスコード `0x090F` で残高+履歴読取 - `launchApp` 等のネイティブヘルパーは除去済み --- ## 既知の不整合・注意点 1. **`JR_shikoku_train_strange`**: app.json に定義済みだが widget-task-handler に専用描画分岐がない(`nameToWidget` にもマッピングなし) 2. **`HelloWidgetPreviewScreen.tsx`**: `HelloWidget` を import しているが該当ファイルが存在しない(死コード) 3. **`testNFC`(settings.tsx)**: スナップショット保存するが `requestWidgetUpdate` を呼ばないためウィジェット未同期 4. **ディープリンク**: `intentFilters` は app.json に追加済みだが**ネイティブビルド未実施**のため未検証