Files
jrshikoku/docs/station-lock-dual-impl.md
harukin-expo-dev-env 8ce0244c4b fix: 駅固定通知の出発済み列車フィルタと棒線駅接近判定を修正
- バックグラウンドで出発済み列車が消えないバグを修正
  (LiveActivityForegroundService: scheduledMinutes+delay<nowMinutes で除外)
- parseTimeToMinutes() ヘルパーを Kotlin に追加
- UI側も遅延加算した発車時刻で出発済み判定するよう統一
  (StationDiagramView: getDelayMinutes + dayjs.add)
- between.ts の BetweenStation を使った接近セクション判定を実装
  (棒線駅で列車がセクション手前に居る場合は未到達扱いで保持)
- StationTrainInfo に sectionStation フィールドを追加
  (prevStop/nextStop を廃止し API Pos と直接照合可能な文字列に変更)
- docs/station-lock-dual-impl.md 作成(KotlinとTS間のデュアル実装ガイド)
2026-03-26 03:00:59 +00:00

7.5 KiB
Raw Permalink Blame History

駅固定(駅ロック)通知バナー — デュアル実装ガイド

KotlinAndroid バックグラウンドサービス)と TypeScriptReact Native UI
2か所に同一ロジックが存在する。言語が異なるため共通化は不可能。
一方を変更したら必ず他方も追従すること。


実装ファイルマップ

役割 ファイル 主要箇所
Android バックグラウンドポーリング・通知更新 modules/expo-live-activity/android/.../LiveActivityForegroundService.kt pollStationTrains()
表示行フォーマット構築Android 同上 buildDisplayLines()
時刻→分変換Android 同上 parseTimeToMinutes()
JS↔Native ブリッジ・表示行フォーマット構築TS modules/expo-live-activity/src/index.ts buildStationLockLines()
React フック(状態管理) lib/useStationLockActivity.ts useStationLockActivity()
UI + nextTrain 探索 + LiveActivity 起動 components/StationDiagram/StationDiagramView.tsx useEffectL218付近

重複ロジック一覧(両側を同期すること)

1. 表示行フォーマット — buildDisplayLinesbuildStationLockLines

フォーマット: 時刻 種別 [列車名] 行き先行 遅延状況(全角スペース区切り)

// LiveActivityForegroundService.kt — buildDisplayLines()
parts.add(t.optString("time", ""))
parts.add(t.optString("typeName", ""))
if (name.isNotEmpty()) parts.add(name)          // trainName
parts.add("${t.optString("destination", "")}行")
parts.add(t.optString("delayStatus", "定刻"))
lines.add(parts.joinToString(" "))             // 全角スペース
// index.ts — buildStationLockLines()
const parts = [t.time, t.typeName];
if (t.trainName) parts.push(t.trainName);
parts.push(`${t.destination}行`);
parts.push(t.delayStatus || '定刻');
return parts.join(' ');                        // 全角スペース

変更時: 両ファイル同時に修正。区切り文字・フィールド順・サフィックスをそろえること。


2. 出発済み列車の除外判定

Kotlinバックグラウンド TypeScriptUI
現在時刻取得 Calendar.getInstance() → 分単位 dayjs()
時刻フォーマット parseTimeToMinutes("HH:mm") dayjs(d.time, "HH:mm")
遅延加算 あり scheduledMinutes + delay >= nowMinutes あり .add(delay, "minute").isAfter(now)
パース失敗時 -1 を返し表示継続(安全側) trainPosition が不完全な場合は時刻判定にフォールバック
接近セクション判定 prevStop + Pos.startsWith("$prevStop") isInApproachSection() (allTrainDiagram + trainPosition)

API の位置報告はセクション単位(例: "橋岡~鴨川")のため、そのセクション内の棒線駅では 遅延が未更新でも列車が残っている可能性がある。セクション内にいることを根拠に表示し続ける。

// LiveActivityForegroundService.kt — pollStationTrains()
val prevStop = t.optString("prevStop", "")          // buildTrainsInfo() で JS 側が付与
val matchedPos = matchedTrain?.optString("Pos", "") ?: ""
val isInApproachSection = prevStop.isNotEmpty() &&
    matchedPos.isNotEmpty() &&
    matchedPos.startsWith("$prevStop")

val scheduledMinutes = parseTimeToMinutes(t.optString("time", ""))
if (scheduledMinutes < 0 || scheduledMinutes + delay >= nowMinutes || isInApproachSection) {
    filtered.put(t)  // 接近中なら時刻を問わず残す
}
// StationDiagramView.tsx — isInApproachSection()
const pos = trainPosition(currentTrain.find(t => t.num === trainNumber));
if (!pos.isBetween) return false;
// allTrainDiagram のルート順で from < 当駅 <= to なら接近セクション内
const stops = allTrainDiagram[trainNumber].split("#").map(e => e.split(",")[0]);
const lo = Math.min(stops.indexOf(pos.Pos.from), stops.indexOf(pos.Pos.to));
const hi = Math.max(stops.indexOf(pos.Pos.from), stops.indexOf(pos.Pos.to));
return stIdx > lo && stIdx <= hi;

// trainTimeFiltering.ts の default: ブロックにも同等ロジック追加済み

prevStopFixedStationBox.tsxbuildTrainsInfo() 内で allTrainDiagram から計算し
trainsJson 各エントリに付与。Android はこの値で Pos 文字列の from 部分を照合する。
上り・下り両対応: allTrainDiagram は走行方向順で格納されているため prevStop も方向自動対応。


3. delayStatus テキスト生成

// LiveActivityForegroundService.kt
"${delay}分遅れ"  // delay > 0
"定刻"            // delay == 0
// index.ts — buildStationLockLines()
t.delayStatus || '定刻'  // Kotlin 側が生成した値を受け取るだけ

Kotlin 側が正規化して書き込み、TS 側は受け取るだけ。
テキストフォーマットを変える場合は Kotlin 側のみ修正すればよい。


データ構造(共通フィールド名)

StationTrainInfotrainsJson の各要素)

フィールド 説明 キー名Kotlin
time "HH:mm" 発車時刻 "time"
typeName string 種別名 "typeName"
trainName string 列車名(なければ "") "trainName"
destination string 行き先 "destination"
platform string 番線 "platform"
delayStatus string "定刻" or "N分遅れ" "delayStatus"
trainNumber string 列車番号(照合用) "trainNumber"

フィールド名はスネークケースではなくキャメルケース。変更時は Kotlin の optString("xxx") とも一致させること。


データフロー

[StationDiagramView.tsx]
  ↓ startStationLock({ trains: StationTrainInfo[], ... })
[index.ts / ExpoLiveActivityModule]
  ↓ ACTION_START_STATION intent (trainsJson, stationTitle, stationColor, ...)
[LiveActivityForegroundService.kt]
  → stationTitle, stationColor, trainsJson を保持
  → 定期ポーリング: pollStationTrains()
      ① API で遅延取得 → delayStatus を上書き
      ② 出発済み列車を除外 → trainsJson を更新
      ③ buildDisplayLines() → 通知を更新

[StationDiagramView.tsx] (フォアグラウンド時)
  → stationLock.update({ nextTrainTime, ... })  ※ trains は渡さない
  → Android 側の trainsJson は更新されない(ポーリングで自律管理)

注意: フォアグラウンド時の stationLock.update()nextTrainTime / nextTrainDestination 等の
StationLockState のみを更新し、trains(詳細リスト)は更新しない。
詳細リストはバックグラウンドポーリングが独立して管理している。


チェックリスト(ロジック修正時)

  • 表示行フォーマットを変えた → buildDisplayLines (Kotlin) と buildStationLockLines (TS) 両方修正
  • 除外閾値を変えた(例: 1分以上余裕を持たせる→ Kotlin pollStationTrains と TS StationDiagramView 両方修正
  • 新フィールドを追加した → StationTrainInfo (TS型定義) と Kotlin の optString キー名を一致させる
  • "定刻" / "N分遅れ" のテキストを変えた → Kotlin 側のみでよいTS は受け取るだけ)