From fb358b53895518038908bfc13dbb1c40717eb965 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Wed, 13 May 2026 22:04:28 +0900 Subject: [PATCH 1/8] =?UTF-8?q?README=20=E3=81=AE=20Prerequisites=20?= =?UTF-8?q?=E7=AF=80=E3=81=AB=E6=B7=B7=E5=9C=A8=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=82=92=E8=8B=B1=E8=A8=B3?= =?UTF-8?q?=20(#5991)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf65e8dca..6fa3e0ecb 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ This app recreates the authentic experience of Japanese train travel by displayi Before you begin, ensure you have met the following requirements: -- **Node.js 22.x** (`.nvmrc` で固定。`nvm use` で自動切り替え) -- **npm 10.x** (Node.js 22 同梱版。`engines` + `engine-strict=true` で他バージョンの `npm install` をブロック) +- **Node.js 22.x** (pinned via `.nvmrc`; run `nvm use` to switch automatically) +- **npm 10.x** (bundled with Node.js 22; `engines` + `engine-strict=true` block `npm install` on other versions) - **React Native development environment** set up - **Expo CLI** installed globally - **Firebase CLI** (for Cloud Functions development) From 62da25123031a79fefd9687499e3de2efa82d9eb Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Fri, 15 May 2026 02:52:04 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E3=83=90=E3=82=B9=E8=B7=AF=E7=B7=9A?= =?UTF-8?q?=E3=81=AE=E5=88=97=E8=BB=8A=E7=A8=AE=E5=88=A5=E3=82=92=E8=87=AA?= =?UTF-8?q?=E5=8B=95=E9=81=B8=E6=8A=9E=E3=81=97=E9=95=B7=E3=81=84=E7=B3=BB?= =?UTF-8?q?=E7=B5=B1=E5=90=8D=E8=A1=A8=E7=A4=BA=E3=81=AB=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=20(#5992)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TrainTypeKind に BusRoute を追加してバス系統の列車種別を表現できるようにする * バス路線で hasTrainTypes が true のとき先頭の列車種別を自動選択する * CommonCard の title を adjustsFontSizeToFit 化しバス系統名は 2 行まで許容する --- src/@types/graphql.d.ts | 1 + src/components/CommonCard.tsx | 7 +- src/constants/simulationMode.ts | 2 + src/hooks/useLineSelection.test.tsx | 105 ++++++++++++++++++++++++++++ src/hooks/useLineSelection.ts | 25 ++++++- 5 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/@types/graphql.d.ts b/src/@types/graphql.d.ts index 29bc4a03a..13c34a957 100644 --- a/src/@types/graphql.d.ts +++ b/src/@types/graphql.d.ts @@ -353,6 +353,7 @@ export type TrainType = { export enum TrainTypeKind { Branch = 'Branch', + BusRoute = 'BusRoute', CommuterRapid = 'CommuterRapid', Default = 'Default', Express = 'Express', diff --git a/src/components/CommonCard.tsx b/src/components/CommonCard.tsx index c5fabb2b8..155b4c149 100644 --- a/src/components/CommonCard.tsx +++ b/src/components/CommonCard.tsx @@ -468,7 +468,12 @@ export const CommonCard: React.FC = ({ )} - + {titlePrefix ? ( {titlePrefix} ) : null} diff --git a/src/constants/simulationMode.ts b/src/constants/simulationMode.ts index bf0e8fad0..066aa96c2 100644 --- a/src/constants/simulationMode.ts +++ b/src/constants/simulationMode.ts @@ -27,6 +27,8 @@ export const TRAIN_TYPE_KIND_MAX_SPEEDS_IN_M_S: Record< [TrainTypeKind.Express]: null, [TrainTypeKind.LimitedExpress]: 36.1111111111, // 130km/h [TrainTypeKind.HighSpeedRapid]: 36.1111111111, // 130km/h + // バス系統は useSimulationMode 側で isBus 分岐により BUS_MAX_SPEED_IN_M_S が適用されるため null + [TrainTypeKind.BusRoute]: null, } as const; // 路線種別による加速度 export const LINE_TYPE_MAX_ACCEL_IN_M_S: Record = { diff --git a/src/hooks/useLineSelection.test.tsx b/src/hooks/useLineSelection.test.tsx index a35b538b9..c4eb7a67b 100644 --- a/src/hooks/useLineSelection.test.tsx +++ b/src/hooks/useLineSelection.test.tsx @@ -3,6 +3,7 @@ import { act, render } from '@testing-library/react-native'; import { useAtomValue, useSetAtom } from 'jotai'; import type React from 'react'; import type { Line, TrainType } from '~/@types/graphql'; +import { TransportType } from '~/@types/graphql'; import { createLine, createStation } from '~/utils/test/factories'; import type { LineState } from '../store/atoms/line'; import type { NavigationState } from '../store/atoms/navigation'; @@ -202,6 +203,110 @@ describe('useLineSelection', () => { expect(navResult.pendingTrainType).toBeNull(); }); + it('バス路線で hasTrainTypes の場合に最初の列車種別を自動選択し lineGroup の駅を取得する', async () => { + const { mockSetStationState, mockSetNavigationState } = setupMolecules(); + const { mockFetchByLineId, mockFetchByGroupId, mockFetchTrainTypes } = + setupQueries(); + + // 路線駅一覧(station.trainType は無い: バスは station 単位で trainType を持たない) + const lineStations = [createStation(10), createStation(20)]; + mockFetchByLineId.mockResolvedValue({ + data: { lineStations }, + }); + + // 自動選択される最初の列車種別と、その lineGroup の駅一覧 + const trainTypes = [ + { id: 1, groupId: 500, name: 'A系統' } as TrainType, + { id: 2, groupId: 501, name: 'B系統' } as TrainType, + ]; + mockFetchTrainTypes.mockResolvedValue({ + data: { stationTrainTypes: trainTypes }, + }); + const groupStations = [createStation(10), createStation(30)]; + mockFetchByGroupId.mockResolvedValue({ + data: { lineGroupStations: groupStations }, + }); + + const line = createLine(100, { + transportType: TransportType.Bus, + station: { id: 10, hasTrainTypes: true } as Line['station'], + }); + + const hookRef: { current: HookResult } = { current: null }; + render( + { + hookRef.current = v; + }} + /> + ); + + await act(async () => { + await hookRef.current?.handleLineSelected(line); + }); + + // 最初の列車種別の groupId で駅一覧を取得している + expect(mockFetchByGroupId).toHaveBeenCalledWith({ + variables: { lineGroupId: 500 }, + }); + + // navigationState の更新で pendingTrainType が先頭の列車種別に設定されている + const navSetterCalls = mockSetNavigationState.mock.calls; + const lastNavSetter = navSetterCalls[navSetterCalls.length - 1][0]; + const navResult = lastNavSetter(createNavigationState()); + expect(navResult.fetchedTrainTypes).toEqual(trainTypes); + expect(navResult.pendingTrainType).toEqual(trainTypes[0]); + + // stationState の最後の更新で lineGroup の駅一覧に差し替わっている + const stationSetterCalls = mockSetStationState.mock.calls; + const lastStationSetter = + stationSetterCalls[stationSetterCalls.length - 1][0]; + const stationResult = lastStationSetter(createStationState()); + expect(stationResult.pendingStations).toEqual(groupStations); + expect(stationResult.pendingStation?.id).toBe(10); + }); + + it('鉄道路線で hasTrainTypes でも station.trainType が無ければ自動選択しない', async () => { + const { mockSetNavigationState } = setupMolecules(); + const { mockFetchByLineId, mockFetchByGroupId, mockFetchTrainTypes } = + setupQueries(); + + const lineStations = [createStation(10)]; + mockFetchByLineId.mockResolvedValue({ + data: { lineStations }, + }); + const trainTypes = [{ id: 1, groupId: 500, name: '快速' } as TrainType]; + mockFetchTrainTypes.mockResolvedValue({ + data: { stationTrainTypes: trainTypes }, + }); + + const line = createLine(100, { + transportType: TransportType.Rail, + station: { id: 10, hasTrainTypes: true } as Line['station'], + }); + + const hookRef: { current: HookResult } = { current: null }; + render( + { + hookRef.current = v; + }} + /> + ); + + await act(async () => { + await hookRef.current?.handleLineSelected(line); + }); + + // 鉄道は自動フォールバックしないので groupId 経由の駅取得は呼ばれない + expect(mockFetchByGroupId).not.toHaveBeenCalled(); + + const navSetterCalls = mockSetNavigationState.mock.calls; + const lastNavSetter = navSetterCalls[navSetterCalls.length - 1][0]; + const navResult = lastNavSetter(createNavigationState()); + expect(navResult.pendingTrainType).toBeNull(); + }); + it('handleTrainTypeSelect が groupId で駅を取得する', async () => { const { mockSetStationState, mockSetNavigationState } = setupMolecules(); const { mockFetchByGroupId } = setupQueries(); diff --git a/src/hooks/useLineSelection.ts b/src/hooks/useLineSelection.ts index c5d033415..c0d62dc57 100644 --- a/src/hooks/useLineSelection.ts +++ b/src/hooks/useLineSelection.ts @@ -11,6 +11,7 @@ import { GET_STATION_TRAIN_TYPES_LIGHT, } from '~/lib/graphql/queries'; import type { SavedRoute } from '~/models/SavedRoute'; +import { isBusLine } from '~/utils/line'; import lineStateAtom from '../store/atoms/line'; import { locationAtom } from '../store/atoms/location'; import navigationState from '../store/atoms/navigation'; @@ -144,15 +145,37 @@ export const useLineSelection = (): UseLineSelectionResult => { const designatedTrainType = fetchedTrainTypes.find((tt) => tt.id === designatedTrainTypeId) ?? null; + // バスは station.trainType を持たないため、最初の列車種別を自動選択する + const fallbackTrainType = + !designatedTrainType && isBusLine(line) + ? (fetchedTrainTypes[0] ?? null) + : null; + const initialTrainType = designatedTrainType ?? fallbackTrainType; + setNavigationState((prev) => ({ ...prev, fetchedTrainTypes, - pendingTrainType: designatedTrainType as TrainType | null, + pendingTrainType: initialTrainType as TrainType | null, })); + + if (fallbackTrainType?.groupId != null) { + const groupResult = await fetchStationsByLineGroupId({ + variables: { lineGroupId: fallbackTrainType.groupId }, + }); + const lineGroupStations = groupResult.data?.lineGroupStations ?? []; + setStationState((prev) => ({ + ...prev, + pendingStations: lineGroupStations, + pendingStation: + lineGroupStations.find((s) => s.id === lineStationId) ?? + prev.pendingStation, + })); + } } }, [ fetchTrainTypes, + fetchStationsByLineGroupId, setNavigationState, setStationState, setLineState, From a9184395b6c798005eedc72b7de6c6a6434f64ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 03:01:17 +0900 Subject: [PATCH 3/8] Bump version for canary release (#5994) Co-authored-by: TinyKitten <32848922+TinyKitten@users.noreply.github.com> --- android/app/build.gradle | 4 +-- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 0531df482..a351d0267 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -141,12 +141,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000420 + versionCode 100000421 versionName "10.5.0" } prod { dimension "environment" - versionCode 100000420 + versionCode 100000421 versionName "10.5.0" } } diff --git a/app.config.ts b/app.config.ts index 6d46a5eb2..d1ea25547 100644 --- a/app.config.ts +++ b/app.config.ts @@ -53,7 +53,7 @@ export default ({ config }: ConfigContext) => ({ }, }, ios: { - buildNumber: '2633', + buildNumber: '2634', bundleIdentifier: process.env.EAS_BUILD_PROFILE === 'production' ? 'me.tinykitten.trainlcd' @@ -70,7 +70,7 @@ export default ({ config }: ConfigContext) => ({ ? 'me.tinykitten.trainlcd' : 'me.tinykitten.trainlcd.dev', permissions: [], - versionCode: 100000420, + versionCode: 100000421, }, owner: 'trainlcd', }); diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 0b7f77392..2855a0eec 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2419,7 +2419,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2458,7 +2458,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2517,7 +2517,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2623,7 +2623,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2702,7 +2702,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2741,7 +2741,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2952,7 +2952,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3003,7 +3003,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3054,7 +3054,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3112,7 +3112,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3163,7 +3163,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3220,7 +3220,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3268,7 +3268,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3319,7 +3319,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3538,7 +3538,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3594,7 +3594,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3644,7 +3644,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3702,7 +3702,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2633; + CURRENT_PROJECT_VERSION = 2634; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 0a9d98ec15412d9c04fd03fe4be532ae6efe7c10 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Fri, 15 May 2026 08:34:33 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E3=83=90=E3=82=B9=E8=B7=AF=E7=B7=9A?= =?UTF-8?q?=E3=81=A7=E5=88=97=E8=BB=8A=E7=A8=AE=E5=88=A5=E3=82=92=E7=B3=BB?= =?UTF-8?q?=E7=B5=B1=E3=81=A8=E8=A8=80=E3=81=84=E6=8F=9B=E3=81=88=E9=95=B7?= =?UTF-8?q?=E3=81=84=E7=B3=BB=E7=B5=B1=E5=90=8D=E3=81=AEtitle=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=82=92=E6=94=B9=E5=96=84=20(#5995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * バス路線では「列車種別」を「系統」と表記するよう言い換え * CommonCardでバス系統名のtitleを最大3行に拡張し末尾の「行」を方面と同様に縮小表示 * バス時の列車種別フォールバック表記を「指定なし」に分岐 --- assets/translations/en.json | 3 + assets/translations/ja.json | 3 + src/components/CommonCard.tsx | 66 ++++++++++++++----- src/components/SelectBoundModal.tsx | 5 +- .../SelectBoundSettingListModal.tsx | 13 +++- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 7dd14a6c9..baa02089d 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -8,6 +8,7 @@ "selectLineTitle": "Please select a line", "settings": "Settings", "settingsAndTrainType": "Settings / Train type", + "settingsAndBusRoute": "Settings / Route", "home": "Home", "selectBoundTitle": "Please select destination", "couldNotGetLocation": "Could not get location information. Please move and try again.", @@ -41,6 +42,7 @@ "back": "Back", "trainTypeSettings": " Train type settings", "trainType": "Train type", + "busRoute": "Route", "trainTypeDefault": "Local", "viewStopStations": "See all stops", "viewBusStops": "See bus stops", @@ -209,6 +211,7 @@ "routeSearchPlaceholder": "Where to?", "trainTypeIs": "Train type: %{trainTypeName}", "trainTypesNotExist": "No train types available", + "busRoutesNotExist": "No routes available", "enableNotificationMode": "Arrival notifications", "betaNotice": "This TrainLCD is currently in beta release.", "canaryNotice": "This TrainLCD is a canary release.", diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 78fd13288..4a16df930 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -8,6 +8,7 @@ "selectLineTitle": "路線を選択してください", "settings": "設定", "settingsAndTrainType": "設定・列車種別", + "settingsAndBusRoute": "設定・系統", "home": "ホーム", "selectBoundTitle": "行先を選択してください", "couldNotGetLocation": "位置情報を取得できませんでした。移動してからもう一度お試しください。", @@ -41,6 +42,7 @@ "back": "戻る", "trainTypeSettings": "列車種別を指定", "trainType": "列車種別", + "busRoute": "系統", "trainTypeDefault": "普通/各駅停車", "viewStopStations": "停車駅を見る", "viewBusStops": "バス停を見る", @@ -210,6 +212,7 @@ "routeSearchPlaceholder": "どこへ行きますか?", "trainTypeIs": "列車種別: %{trainTypeName}", "trainTypesNotExist": "列車種別がありません", + "busRoutesNotExist": "系統がありません", "enableNotificationMode": "駅到着時に通知", "betaNotice": "このTrainLCDは現在ベータリリースです。", "canaryNotice": "このTrainLCDはカナリアリリースです。", diff --git a/src/components/CommonCard.tsx b/src/components/CommonCard.tsx index 155b4c149..cba83af68 100644 --- a/src/components/CommonCard.tsx +++ b/src/components/CommonCard.tsx @@ -358,12 +358,33 @@ export const CommonCard: React.FC = ({ } } } + // バス系統名が title として渡された場合は「行」も「方面」と同様に縮小する。 + // 「池袋駅東口行(新宿伊勢丹前経由・循環)」のように後続にカッコが続くケースに対応するため、 + // カッコで分割した各セグメントの末尾の「行」を切り出す。 + const shouldShrinkYuki = isBus && !!title && isJapanese; + const parts: { text: string; kind: 'main' | 'parens' | 'affix' }[] = []; + for (const seg of main.split(PAREN_GROUP_REGEX)) { + if (!seg) continue; + if (PAREN_WRAPPED_REGEX.test(seg)) { + parts.push({ text: seg, kind: 'parens' }); + continue; + } + if (shouldShrinkYuki) { + const match = /^(.+?)(行)$/.exec(seg); + if (match) { + if (match[1]) parts.push({ text: match[1], kind: 'main' }); + parts.push({ text: match[2], kind: 'affix' }); + continue; + } + } + parts.push({ text: seg, kind: 'main' }); + } return { titlePrefix: prefix, - titleParts: main.split(PAREN_GROUP_REGEX), + titleParts: parts, titleSuffix: suffix, }; - }, [titleOrLineName, shrinkBoundAffix]); + }, [titleOrLineName, shrinkBoundAffix, isBus, title]); const additionalRootStyle = useMemo( () => ({ @@ -470,25 +491,38 @@ export const CommonCard: React.FC = ({ {titlePrefix ? ( {titlePrefix} ) : null} - {titleParts.map((part, index) => - PAREN_WRAPPED_REGEX.test(part) ? ( - - {index > 0 && !/\s$/.test(titleParts[index - 1] ?? '') - ? ' ' - : ''} - {hideParens ? part.slice(1, -1) : part} - - ) : ( - part - ) - )} + {titleParts.map((part, index) => { + if (part.kind === 'parens') { + const prevText = titleParts[index - 1]?.text ?? ''; + return ( + + {index > 0 && !/\s$/.test(prevText) ? ' ' : ''} + {hideParens ? part.text.slice(1, -1) : part.text} + + ); + } + if (part.kind === 'affix') { + return ( + + {part.text} + + ); + } + return part.text; + })} {titleSuffix ? ( {titleSuffix} ) : null} diff --git a/src/components/SelectBoundModal.tsx b/src/components/SelectBoundModal.tsx index 384b921d5..4b8fb60f1 100644 --- a/src/components/SelectBoundModal.tsx +++ b/src/components/SelectBoundModal.tsx @@ -911,7 +911,9 @@ export const SelectBoundModal: React.FC = ({ > {translate( fetchedTrainTypes.length > 1 - ? 'settingsAndTrainType' + ? isBus + ? 'settingsAndBusRoute' + : 'settingsAndTrainType' : 'settings' )} @@ -985,6 +987,7 @@ export const SelectBoundModal: React.FC = ({ } }} trainTypeDisabled={fetchedTrainTypes.length <= 1} + isBus={isBus} /> void; trainTypeDisabled?: boolean; + isBus?: boolean; themeLabel?: string; themeColor?: string; onThemePress?: () => void; @@ -143,6 +144,7 @@ export const SelectBoundSettingListModal: React.FC = ({ trainTypeLoading, onTrainTypePress, trainTypeDisabled, + isBus, themeLabel, themeColor, onThemePress, @@ -221,12 +223,14 @@ export const SelectBoundSettingListModal: React.FC = ({ > {trainTypeDisabled ? ( - {translate('trainTypesNotExist')} + {translate( + isBus ? 'busRoutesNotExist' : 'trainTypesNotExist' + )} ) : ( <> - {translate('trainType')} + {translate(isBus ? 'busRoute' : 'trainType')} = ({ numberOfLines={1} > {renderWithSmallParens( - trainTypeName || translate('trainTypeDefault'), + trainTypeName || + translate( + isBus ? 'notSpecified' : 'trainTypeDefault' + ), trainTypeTextColor )} From 25fe0c889d78a961428fda0e91cc4a2639fb21c1 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Fri, 15 May 2026 08:43:05 +0900 Subject: [PATCH 5/8] =?UTF-8?q?TOEI=E3=83=90=E3=82=B9TTS=E3=81=A7=E3=80=8C?= =?UTF-8?q?=E3=81=93=E3=81=AE=E3=83=90=E3=82=B9=E3=81=AF=E3=80=9C=E3=82=86?= =?UTF-8?q?=E3=81=8D=E3=81=A7=E3=81=99=E3=80=8D=E3=81=8C=E6=AF=8E=E5=9B=9E?= =?UTF-8?q?=E5=86=8D=E7=94=9F=E3=81=95=E3=82=8C=E3=82=8B=E4=B8=8D=E5=85=B7?= =?UTF-8?q?=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3=20(#5996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useBusTTSText.test.tsx | 19 +++++++++++++++++++ src/hooks/useBusTTSText.ts | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/hooks/useBusTTSText.test.tsx b/src/hooks/useBusTTSText.test.tsx index e0380bcd1..763355e09 100644 --- a/src/hooks/useBusTTSText.test.tsx +++ b/src/hooks/useBusTTSText.test.tsx @@ -332,6 +332,25 @@ describe('useBusTTSText', () => { expect(enText).toContain('This bus is bound for'); }); + // 回帰テスト: TOEIの「このバスは、〜ゆきです。」/ "This bus is bound for ..." は + // 初回放送 (firstSpeech=true) のみで再生される必要がある。 + // 以前はテンプレート構造の都合で firstSpeech 条件の外に出ており、停車のたびに + // 繰り返し再生されていた。 + test('should omit bound-for phrase on subsequent NEXT (firstSpeech=false)', () => { + const { result } = renderHook( + () => useBusTTSTextWithJotai('TOEI', 'NEXT', false), + { + wrapper: wrapper, + } + ); + const [jaText, enText] = result.current.text; + expect(jaText).toContain('次は'); + expect(jaText).not.toContain('このバスは'); + expect(jaText).not.toContain('ご利用くださいまして'); + expect(enText).not.toContain('This bus is bound for'); + expect(enText).not.toContain('Thank you for using the'); + }); + test('should generate ARRIVING text', () => { const { result } = renderHook( () => useBusTTSTextWithJotai('TOEI', 'ARRIVING'), diff --git a/src/hooks/useBusTTSText.ts b/src/hooks/useBusTTSText.ts index 2c433d4ae..409f05943 100644 --- a/src/hooks/useBusTTSText.ts +++ b/src/hooks/useBusTTSText.ts @@ -446,7 +446,7 @@ export const useBusTTSText = ( }、${ replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? '' - }。このバスは、${boundForJa}ゆきです。${ + }。${firstSpeech ? `このバスは、${boundForJa}ゆきです。` : ''}${ afterNextStation ? `${replaceJapaneseText( nextStation?.name, @@ -690,9 +690,9 @@ export const useBusTTSText = ( [APP_THEME.TOEI]: { NEXT: `${ firstSpeech - ? `Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}. ` + ? `Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}. This bus is bound for ${boundForEn}. ` : '' - }This bus is bound for ${boundForEn}. The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.`, + }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.`, ARRIVING: `We will soon be arriving at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.${ afterNextStation ? ` The stop after ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}, will be ${ph( From 86dcb764f7e39c2b6bd6965602820aac84395151 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Fri, 15 May 2026 09:36:28 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E3=83=90=E3=82=B9TTS=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=82=92=E9=89=84=E9=81=93TTS=E5=AE=9F=E8=A3=85=E3=81=AB?= =?UTF-8?q?=E7=B5=B1=E5=90=88=E3=81=97=E5=85=B1=E9=80=9A=E3=83=86=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E3=81=A7=E9=9F=B3=E5=A3=B0?= =?UTF-8?q?=E3=82=92=E7=94=9F=E6=88=90=20(#5997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/tts/templates.ts | 87 +- src/hooks/useBusTTSText.ts | 788 ------------------ src/hooks/useTTS.test.ts | 7 - src/hooks/useTTS.ts | 9 +- ...SText.test.tsx => useTTSText.bus.test.tsx} | 10 +- src/hooks/useTTSText.ts | 67 +- 6 files changed, 110 insertions(+), 858 deletions(-) delete mode 100644 src/hooks/useBusTTSText.ts rename src/hooks/{useBusTTSText.test.tsx => useTTSText.bus.test.tsx} (97%) diff --git a/src/hooks/tts/templates.ts b/src/hooks/tts/templates.ts index f813d0cf2..efef34aae 100644 --- a/src/hooks/tts/templates.ts +++ b/src/hooks/tts/templates.ts @@ -11,10 +11,10 @@ const TOKYO_METRO: ThemeTemplate = { '{#if firstSpeech}', '{currentLineJa}をご利用くださいまして、ありがとうございます。', '次は、{nextStationJa}{#if isNextStopTerminus}、終点{/if}です。', - 'この電車は、', + '{vehicleJa}は、', '{#if hasConnectedLines}{connectedLinesListJa}直通、{/if}', - '{trainTypeJa}、{boundForJa}ゆきです。', - '{#if hasTrainTypeAndAfterNext}', + '{#if hasTrainType}{trainTypeJa}、{/if}{boundForJa}ゆきです。', + '{#if shouldAnnounceAfterNextStop}', '{nextStationJa}の次は、', '{#if isAfterNextStopTerminus}終点、{/if}', '{afterNextStationJa}に停まります。', @@ -23,7 +23,7 @@ const TOKYO_METRO: ThemeTemplate = { '{:else}', '次は、{nextStationJa}{#if isNextStopTerminus}、終点{/if}です。', '{#if hasTransferLines}{transferLinesListJa}はお乗り換えです。{/if}', - '{#if hasTrainTypeAndAfterNext}', + '{#if shouldAnnounceAfterNextStop}', '{nextStationJa}の次は、', '{#if isAfterNextStopTerminus}終点、{/if}', '{afterNextStationJa}に停まります。', @@ -42,9 +42,9 @@ const TY: ThemeTemplate = { NEXT: t( '{#if firstSpeech}', '{currentLineJa}をご利用くださいまして、ありがとうございます。', - 'この電車は', + '{vehicleJa}は', '{#if hasConnectedLines}{connectedLinesListJa}直通、{/if}', - '{trainTypeJa}、{boundForJa}ゆきです。', + '{#if hasTrainType}{trainTypeJa}、{/if}{boundForJa}ゆきです。', '{/if}', '次は、{nextStationJa}{#if isNextStopTerminus}、終点{/if}です。', '{#if hasTransferLines}{transferLinesListJa}をご利用のお客様はお乗り換えです。{/if}' @@ -63,7 +63,7 @@ const TY: ThemeTemplate = { const YAMANOTE: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}今日も、{currentLineCompanyShortJa}をご利用くださいまして、ありがとうございます。この電車は、{boundForJa}ゆきです。{/if}', + '{#if firstSpeech}今日も、{currentLineCompanyShortJa}をご利用くださいまして、ありがとうございます。{vehicleJa}は、{boundForJa}ゆきです。{/if}', '次は、{nextStationJa}、{nextStationJa}{#if isNextStopTerminus}、終点です{/if}。', '{#if hasTransferLines}{transferLinesListJa}はお乗り換えです。{/if}' ), @@ -76,7 +76,7 @@ const YAMANOTE: ThemeTemplate = { const SAIKYO: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}今日も、{currentLineCompanyShortJa}をご利用くださいまして、ありがとうございます。この電車は、{boundForJa}ゆきです。{/if}', + '{#if firstSpeech}今日も、{currentLineCompanyShortJa}をご利用くださいまして、ありがとうございます。{vehicleJa}は、{boundForJa}ゆきです。{/if}', '次は、{#if isNextStopTerminus}終点、{/if}{nextStationJa}、{nextStationJa}。', '{#if hasTransferLines}{transferLinesListJa}は、お乗り換えです。{/if}' ), @@ -91,7 +91,7 @@ const JR_WEST: ThemeTemplate = { NEXT: t( '{#if firstSpeech}', '今日も、{currentLineCompanyShortJa}をご利用くださいまして、ありがとうございます。', - 'この電車は、{trainTypeJaPlain}、', + '{vehicleJa}は、{#if hasTrainType}{trainTypeJaPlain}、{/if}', '{#if hasViaStation}{viaStationJa}方面、{/if}', '{boundForJa}ゆきです。', '{/if}', @@ -123,10 +123,12 @@ const TOEI: ThemeTemplate = { '{#if firstSpeech}{currentLineJa}をご利用くださいまして、ありがとうございます。{/if}', '次は、{nextStationJa}、{nextStationJa}{#if isNextStopTerminus}、終点です{/if}。 ', '{#if hasTransferLines}{transferLinesListJa}はお乗り換えです。{/if}', - 'この電車は、', + '{#if announceVehicleSummary}', + '{vehicleJa}は、', '{#if hasConnectedLines}{connectedLinesListJa}直通、{/if}', - '{trainTypeJa}、{boundForJa}ゆきです。', - '{#if hasTrainTypeAndAfterNext}', + '{#if hasTrainType}{trainTypeJa}、{/if}{boundForJa}ゆきです。', + '{/if}', + '{#if shouldAnnounceAfterNextStop}', '{nextStationJa}の次は、', '{#if isAfterNextStopTerminus}終点、{/if}', '{afterNextStationJa}に停まります。', @@ -136,7 +138,7 @@ const TOEI: ThemeTemplate = { ARRIVING: t( 'まもなく、{#if isNextStopTerminus}終点、{/if}{nextStationJa}、{nextStationJa}。', '{#if hasTransferLines}{transferLinesListJa}はお乗り換えです。{/if}', - '{#if hasTrainTypeAndAfterNext}', + '{#if shouldAnnounceAfterNextStop}', '{nextStationJa}の次は、', '{#if isAfterNextStopTerminus}終点、{/if}', '{afterNextStationJa}に停まります。', @@ -148,7 +150,7 @@ const TOEI: ThemeTemplate = { const JR_KYUSHU: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}この列車は{trainTypeJaKyushu}、{boundForJa}行きです。{/if}', + '{#if firstSpeech}{vehicleJa}は{#if hasTrainType}{trainTypeJaKyushu}、{/if}{boundForJa}行きです。{/if}', '次は{#if nextStationIsBound}終点、{/if}{nextStationJa}、{nextStationJa}。', '{#if hasTransferLines}{nextStationJa}では、{transferLinesListJa}にお乗り換えいただけます。{/if}' ), @@ -166,34 +168,33 @@ const TOKYO_METRO_EN: ThemeTemplate = { '{#if isNextStopTerminus} The last stop.{/if}', '{#if hasTransferLines} Please change here for {transferLinesEnList}.{/if}', '{#if firstSpeech}', - ' This train is the ', - '{#if yamanoteTrainTypeEn}{yamanoteTrainTypeEn} train', - '{:else}{currentTrainTypeOrLocalEn} Service on the {currentLineEn}{/if}', + ' This {vehicleEn} is', + '{#if yamanoteTrainTypeEn} the {yamanoteTrainTypeEn} {vehicleEn}', + '{:else}{#if hasTrainType} the {currentTrainTypeOrLocalEn} Service{/if} on the {currentLineEn}{/if}', ' bound for {boundForEn}. ', - '{#if hasTrainTypeAndAfterNext}', + '{#if shouldAnnounceAfterNextStop}', 'The next stop after {nextStationEn}, is {afterNextStationEn}', '{#if isAfterNextStopTerminus} terminal{/if}.', '{/if}', - '{#if hasBetweenStations} For stations in between, Please change trains at the next stop.{/if}', + '{#if hasBetweenStations} For stations in between, Please change {vehicleEnPlural} at the next stop.{/if}', '{/if}' ), ARRIVING: t( 'Arriving at {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, the last stop.{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}{/if}. ', - '{#if isNextStopTerminus}Thank you for using the {currentLineEn}.{/if}' + '{#if isNextStopTerminus}Thank you for using the {currentLineThanksEn}.{/if}' ), }; const TY_EN: ThemeTemplate = { NEXT: t( '{#if firstSpeech}', - 'Thank you for using the {currentLineEn}. ', - 'This is the {trainTypeEn} train ', - '{connectedLineEnPhrase}', - ' to {boundForEn}. ', + 'Thank you for using the {currentLineThanksEn}. ', + '{#if hasTrainType}This is the {trainTypeEn} {vehicleEn} {connectedLineEnPhrase} to {boundForEn}. ', + '{:else}This {vehicleEn} is bound for {boundForEn}. {/if}', '{/if}', - 'The next station is {nextStationEn} {nextStationNumberText}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, the last stop{/if} ', '{#if hasTransferLines}Passengers changing to {transferLinesEnList}, Please transfer at this station.{/if}' ), @@ -201,20 +202,20 @@ const TY_EN: ThemeTemplate = { 'We will soon make a brief stop at {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, the last stop{/if}', '{#if hasTransferLines} Passengers changing to {transferLinesEnList}, Please transfer at this station.{/if}', - '{#if hasTrainTypeAndAfterNext} The stop after {nextStationEn}, will be {afterNextStationEn}{#if isAfterNextStopTerminus} the last stop{/if}.{/if}', - '{#if isNextStopTerminus} Thank you for using the {currentLineEn}.{/if}' + '{#if shouldAnnounceAfterNextStop} The stop after {nextStationEn}, will be {afterNextStationEn}{#if isAfterNextStopTerminus} the last stop{/if}.{/if}', + '{#if isNextStopTerminus} Thank you for using the {currentLineThanksEn}.{/if}' ), }; const YAMANOTE_EN: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}This is the {currentLineEn} train bound for {boundForEn}. {/if}', - 'The next station is {nextStationEn} {nextStationNumberText}', + '{#if firstSpeech}This is the {currentLineEn} {vehicleEn} bound for {boundForEn}. {/if}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, terminal.{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}.{/if}' ), ARRIVING: t( - 'The next station is {nextStationEn} {nextStationNumberText}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, terminal.{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}{/if}. ', '{#if isNextStopTerminus}Thank you for traveling with us, and look forward to serving you again.{/if}' @@ -223,13 +224,13 @@ const YAMANOTE_EN: ThemeTemplate = { const SAIKYO_EN: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}This is the {currentLineEn} train bound for {boundForEn}. {/if}', - 'The next station is {nextStationEn} {nextStationNumberText}', + '{#if firstSpeech}This is the {currentLineEn} {vehicleEn} bound for {boundForEn}. {/if}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, terminal{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}.{/if}' ), ARRIVING: t( - 'The next station is {nextStationEn} {nextStationNumberText}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, terminal.{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}.{/if} ', '{#if isNextStopTerminus}Thank you for traveling with us, and look forward to serving you again.{/if}' @@ -240,8 +241,8 @@ const JR_WEST_EN: ThemeTemplate = { NEXT: t( '{#if firstSpeech}', 'Thank you for using {currentLineCompanyEn}. ', - 'This is the {trainTypeEn} Service bound for {boundForEn} ', - '{#if hasViaStation}via {viaStationEn}{/if}. ', + '{#if hasTrainType}This is the {trainTypeEn} Service{:else}This {vehicleEn} is{/if} bound for {boundForEn}', + '{#if hasViaStation} via {viaStationEn}{/if}. ', '{/if}', '{#if shouldAnnounceJrWestStopList}', 'We will be stopping at {jrWestStopsListEn}. ', @@ -261,9 +262,9 @@ const JR_WEST_EN: ThemeTemplate = { const TOEI_EN: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}Thank you for using the {currentLineEn}. {/if}', - 'This is the {trainTypeEn} train bound for {boundForEn}. ', - 'The next station is {nextStationEn} {nextStationNumberText}', + '{#if firstSpeech}Thank you for using the {currentLineThanksEn}. {/if}', + '{#if announceVehicleSummary}{#if hasTrainType}This is the {trainTypeEn} {vehicleEn}{:else}This {vehicleEn} is{/if} bound for {boundForEn}. {/if}', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, the last stop{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}.{/if}' ), @@ -271,15 +272,15 @@ const TOEI_EN: ThemeTemplate = { 'We will soon be arriving at {nextStationEn} {nextStationNumberText}', '{#if isNextStopTerminus}, the last stop{/if} ', '{#if hasTransferLines}Please change here for {transferLinesEnList}.{/if}', - '{#if hasTrainTypeAndAfterNext} The stop after {nextStationEn}, will be {afterNextStationEn}{#if isAfterNextStopTerminus} the last stop{/if}.{/if}', - '{#if isNextStopTerminus} Thank you for using the {currentLineEn}.{/if}' + '{#if shouldAnnounceAfterNextStop} The stop after {nextStationEn}, will be {afterNextStationEn}{#if isAfterNextStopTerminus} the last stop{/if}.{/if}', + '{#if isNextStopTerminus} Thank you for using the {currentLineThanksEn}.{/if}' ), }; const JR_KYUSHU_EN: ThemeTemplate = { NEXT: t( - '{#if firstSpeech}This is a {trainTypeEn} train bound for {boundForEn}.{/if} ', - 'The next station is {nextStationEn} {nextStationNumberText}', + '{#if firstSpeech}{#if hasTrainType}This is a {trainTypeEn} {vehicleEn}{:else}This {vehicleEn} is{/if} bound for {boundForEn}.{/if} ', + 'The next {placeEn} is {nextStationEn} {nextStationNumberText}', '{#if nextStationIsBound} terminal{/if}. ', '{#if hasTransferLines}You can transfer to {transferLinesEnList} at {nextStationEn}.{/if}' ), @@ -288,7 +289,7 @@ const JR_KYUSHU_EN: ThemeTemplate = { '{#if nextStationIsBound} terminal{/if} {nextStationNumberText}. ', '{#if hasTransferLines}', 'You can transfer to {transferLinesEnList} at {nextStationEn}. ', - '{#if nextStationIsBound}Thank you for using the {currentLineEn}.{/if}', + '{#if nextStationIsBound}Thank you for using the {currentLineThanksEn}.{/if}', '{/if}' ), }; diff --git a/src/hooks/useBusTTSText.ts b/src/hooks/useBusTTSText.ts deleted file mode 100644 index 409f05943..000000000 --- a/src/hooks/useBusTTSText.ts +++ /dev/null @@ -1,788 +0,0 @@ -import { useAtomValue } from 'jotai'; -import { useCallback, useMemo } from 'react'; -import type { Maybe, Station } from '~/@types/graphql'; -import { parenthesisRegexp } from '../constants'; -import { APP_THEME, type AppTheme } from '../models/Theme'; -import stationState from '../store/atoms/station'; -import { themeAtom } from '../store/atoms/theme'; -import getIsPass from '../utils/isPass'; -import katakanaToHiragana from '../utils/kanaToHiragana'; -import { wrapPhoneme as ph } from '../utils/phoneme'; -import { stripStationParensForTTS } from './tts/formatters'; -import { useAfterNextStation } from './useAfterNextStation'; -import { useBounds } from './useBounds'; -import { useCurrentLine } from './useCurrentLine'; -import { useCurrentStation } from './useCurrentStation'; -import { useIsTerminus } from './useIsTerminus'; -import { useLoopLine } from './useLoopLine'; -import { useLoopLineBound } from './useLoopLineBound'; -import { useNextStation } from './useNextStation'; -import { useSlicedStations } from './useSlicedStations'; -import { useStoppingState } from './useStoppingState'; -import type { TTSTextResult } from './useTTSText'; - -const EMPTY_TTS_TEXT = { - [APP_THEME.TOKYO_METRO]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.TY]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.YAMANOTE]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.JR_WEST]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.SAIKYO]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.TOEI]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.LED]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.JO]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.JL]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.JR_KYUSHU]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.ODAKYU]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.E231]: { NEXT: '', ARRIVING: '' }, -}; - -const resolveTemplateTheme = (theme: AppTheme): AppTheme => { - if (theme === APP_THEME.LED || theme === APP_THEME.ODAKYU) - return APP_THEME.TOKYO_METRO; - if ( - theme === APP_THEME.JO || - theme === APP_THEME.JL || - theme === APP_THEME.E231 - ) - return APP_THEME.YAMANOTE; - return theme; -}; - -export const useBusTTSText = ( - firstSpeech = true, - enabled = false -): TTSTextResult => { - const theme = useAtomValue(themeAtom); - - const { selectedBound: selectedBoundOrigin, stations } = - useAtomValue(stationState); - const station = useCurrentStation(); - const currentLineOrigin = useCurrentLine(); - - const loopLineBoundJa = useLoopLineBound(false); - const loopLineBoundEn = useLoopLineBound(false, 'EN'); - const { directionalStops } = useBounds(stations); - const nextStationOrigin = useNextStation(); - const isNextStopTerminus = useIsTerminus(nextStationOrigin); - const { isLoopLine, isPartiallyLoopLine } = useLoopLine(); - const slicedStationsOrigin = useSlicedStations(); - const stoppingState = useStoppingState(); - - const replaceJapaneseText = useCallback( - (name: Maybe, nameKatakana: Maybe) => - `${name}`, - [] - ); - - const currentLine = useMemo( - () => currentLineOrigin ?? null, - [currentLineOrigin] - ); - - const selectedBound = useMemo( - () => selectedBoundOrigin ?? null, - [selectedBoundOrigin] - ); - - // directionalStops のカッコ書きも TTS で読み上げないため、 - // boundForJa / boundForEn に渡す前に取り除く (issue #1175)。 - const directionalStopsForTTS = useMemo( - () => directionalStops.map(stripStationParensForTTS), - [directionalStops] - ); - - const boundForJa = useMemo( - () => - isLoopLine - ? // NOTE: メジャーな駅だからreplaceJapaneseTextは要らない...はず - loopLineBoundJa?.boundFor?.replace(/・/g, '') - : replaceJapaneseText( - `${directionalStopsForTTS.map((s) => s?.name).join('・')}${ - isPartiallyLoopLine ? '方面' : '' - }`, - `${directionalStopsForTTS.map((s) => s?.nameKatakana).join('・')}${ - isPartiallyLoopLine ? 'ホウメン' : '' - }` - ), - [ - directionalStopsForTTS, - isLoopLine, - isPartiallyLoopLine, - loopLineBoundJa?.boundFor, - replaceJapaneseText, - ] - ); - - const boundForEn = useMemo( - () => - isLoopLine - ? (loopLineBoundEn?.boundFor?.replaceAll('&', ' and ') ?? '') - : directionalStopsForTTS - .map((s) => ph(s?.nameTtsSegments, s?.nameRoman)) - .join(' and '), - [directionalStopsForTTS, isLoopLine, loopLineBoundEn?.boundFor] - ); - - // 駅名に併記されたカッコ書き (命名権スポンサー名など) を TTS で読み上げないため、 - // template に渡す手前で TTS 関連フィールドからカッコと中身を落としておく - // (issue #1175)。表示用データには影響させない。 - const nextStation = useMemo( - () => - nextStationOrigin ? stripStationParensForTTS(nextStationOrigin) : null, - [nextStationOrigin] - ); - - // 直通時、同じGroupIDの駅が違う駅として扱われるのを防ぐ(ex. 渋谷の次は渋谷に止まります) - const slicedStations = useMemo( - () => - Array.from(new Set(slicedStationsOrigin.map((s) => s.groupId))) - .map((gid) => slicedStationsOrigin.find((s) => s.groupId === gid)) - .filter((s): s is Station => !!s) - .map(stripStationParensForTTS), - [slicedStationsOrigin] - ); - - const afterNextStationOrigin = useAfterNextStation(); - const afterNextStation = useMemo( - () => - afterNextStationOrigin - ? stripStationParensForTTS(afterNextStationOrigin) - : undefined, - [afterNextStationOrigin] - ); - - const nextStationIndex = useMemo( - () => slicedStations.findIndex((s) => s.groupId === nextStation?.groupId), - [nextStation?.groupId, slicedStations] - ); - const afterNextStationIndex = useMemo( - () => - slicedStations.findIndex((s) => s.groupId === afterNextStation?.groupId), - [afterNextStation?.groupId, slicedStations] - ); - - const betweenNextStation = useMemo( - () => - nextStationIndex === -1 || - afterNextStationIndex === -1 || - afterNextStationIndex <= nextStationIndex - ? [] - : slicedStations.slice(nextStationIndex + 1, afterNextStationIndex), - [afterNextStationIndex, nextStationIndex, slicedStations] - ); - - const isAfterNextStopTerminus = useIsTerminus(afterNextStation); - - const allStops = useMemo( - () => - slicedStations.filter((s) => { - if (s.groupId === station?.groupId) { - return false; - } - return !getIsPass(s); - }), - [slicedStations, station] - ); - - const viaStation = useMemo(() => { - const sortedStops = allStops - .slice() - .sort((a, b) => - (a.lines?.length ?? 0) < (b.lines?.length ?? 0) ? 1 : -1 - ); - - if (allStops[allStops.length - 1]?.id === sortedStops[0]?.id) { - return; // 終着駅と同じ駅の場合undefinedを返す - } - return sortedStops[0]; - }, [allStops]); - - const japaneseTemplate: Record | null = - useMemo(() => { - if (!currentLine || !selectedBound) { - return EMPTY_TTS_TEXT; - } - - const map = { - [APP_THEME.TOKYO_METRO]: { - NEXT: firstSpeech - ? `${replaceJapaneseText( - currentLine.nameShort, - currentLine.nameKatakana - )}をご利用くださいまして、ありがとうございます。次は、${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}です。このバスは、${boundForJa}ゆきです。${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}の次は、${ - isAfterNextStopTerminus ? '終点、' : '' - }${replaceJapaneseText( - afterNextStation?.name, - afterNextStation?.nameKatakana - )}に停まります。` - : '' - }${ - betweenNextStation.length - ? `${betweenNextStation - .map((s) => replaceJapaneseText(s.name, s.nameKatakana)) - .join('、')}へおいでのお客様はお乗り換えです。` - : '' - }` - : `次は、${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}${isNextStopTerminus ? '、終点' : ''}です。${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}の次は、${ - isAfterNextStopTerminus ? '終点、' : '' - }${replaceJapaneseText( - afterNextStation?.name, - afterNextStation?.nameKatakana - )}に停まります。` - : '' - }${ - betweenNextStation.length - ? `${betweenNextStation - .map((s) => replaceJapaneseText(s.name, s.nameKatakana)) - .join('、')}へおいでのお客様はお乗り換えです。` - : '' - }`, - ARRIVING: `まもなく、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }${isNextStopTerminus ? '終点' : ''}です。${ - isNextStopTerminus - ? `${replaceJapaneseText( - currentLine.company?.nameShort, - currentLine.company?.nameKatakana - )}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.TY]: { - NEXT: `${ - firstSpeech - ? `${replaceJapaneseText( - currentLine.nameShort, - currentLine.nameKatakana - )}をご利用くださいまして、ありがとうございます。このバスは${boundForJa}ゆきです。` - : '' - }次は、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }${isNextStopTerminus ? '、終点' : ''}です。`, - ARRIVING: `まもなく、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }${isNextStopTerminus ? '、終点' : ''}です。${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}を出ますと、${ - isAfterNextStopTerminus ? '終点、' : '' - }${replaceJapaneseText( - afterNextStation.name, - afterNextStation.nameKatakana - )}に停まります。` - : '' - }${ - isNextStopTerminus - ? ` ${replaceJapaneseText( - currentLine?.nameShort, - currentLine?.nameKatakana - )}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.YAMANOTE]: { - NEXT: `${ - firstSpeech - ? `今日も、${currentLine.company?.nameShort}をご利用くださいまして、ありがとうございます。このバスは、${boundForJa}ゆきです。` - : '' - }次は、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }${isNextStopTerminus ? '、終点です' : ''}。`, - ARRIVING: `まもなく、${isNextStopTerminus ? '終点、' : ''}${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }。${ - isNextStopTerminus - ? `${currentLine.company?.nameShort}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.JO]: { - NEXT: '', - ARRIVING: '', - }, - [APP_THEME.JL]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.SAIKYO]: { - NEXT: `${ - firstSpeech - ? `今日も、${currentLine.company?.nameShort}をご利用くださいまして、ありがとうございます。このバスは、${boundForJa}ゆきです。` - : '' - }次は、${isNextStopTerminus ? '終点、' : ''}${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }。`, - ARRIVING: `まもなく、${isNextStopTerminus ? '終点、' : ''}${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }。${ - isNextStopTerminus - ? `${currentLine.company?.nameShort}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.JR_WEST]: { - NEXT: `${ - firstSpeech - ? `今日も、${ - currentLine.company?.nameShort - }をご利用くださいまして、ありがとうございます。このバスは、${ - viaStation - ? `${replaceJapaneseText( - viaStation.name, - viaStation.nameKatakana - )}方面、` - : '' - }${boundForJa}ゆきです。${allStops - .slice(0, 5) - .map((s) => - s.id === selectedBound?.id && !isLoopLine - ? `終点、${replaceJapaneseText(s.name, s.nameKatakana)}` - : replaceJapaneseText(s.name, s.nameKatakana) - ) - .join('、')}の順に停まります。${ - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.id === selectedBound?.id - ? '' - : `${replaceJapaneseText( - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.name, - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.nameKatakana - )}から先は、後ほどご案内いたします。` - }` - : '' - }次は、${nextStation?.groupId === selectedBound?.groupId && !isLoopLine ? '終点、' : ''}${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }です。`, - ARRIVING: isNextStopTerminus - ? `ご乗車ありがとうございました。まもなく${ - replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - ) ?? '' - }、${ - replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - ) ?? '' - }です。今日も${currentLine.company?.nameShort}をご利用くださいまして、ありがとうございました。${replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana)}、${replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana)}です。` - : `まもなく、${ - replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - ) ?? '' - }、${ - replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - ) ?? '' - }です。${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}を出ますと、次は、${replaceJapaneseText( - afterNextStation.name, - afterNextStation.nameKatakana - )}に停まります。` - : '' - }`, - }, - [APP_THEME.TOEI]: { - NEXT: `${ - firstSpeech - ? `${replaceJapaneseText( - currentLine.nameShort, - currentLine.nameKatakana - )}をご利用くださいまして、ありがとうございます。` - : '' - }次は、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }。${firstSpeech ? `このバスは、${boundForJa}ゆきです。` : ''}${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}の次は、${ - isAfterNextStopTerminus ? '終点、' : '' - }${replaceJapaneseText( - afterNextStation?.name, - afterNextStation?.nameKatakana - )}に停まります。` - : '' - }${ - betweenNextStation.length - ? `通過する、${betweenNextStation - .map((s) => replaceJapaneseText(s.name, s.nameKatakana)) - .join('、')}へおいでの方はお乗り換えです。` - : '' - }`, - ARRIVING: `まもなく、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }、${ - replaceJapaneseText(nextStation?.name, nextStation?.nameKatakana) ?? - '' - }。${ - afterNextStation - ? `${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}の次は、${ - isAfterNextStopTerminus ? '終点、' : '' - }${replaceJapaneseText( - afterNextStation?.name, - afterNextStation?.nameKatakana - )}に停まります。` - : '' - }${ - betweenNextStation.length - ? `通過する、${betweenNextStation - .map((s) => replaceJapaneseText(s.name, s.nameKatakana)) - .join('、')}へおいでの方はお乗り換えです。` - : '' - }${ - isNextStopTerminus - ? ` ${replaceJapaneseText( - currentLine?.nameShort, - currentLine?.nameKatakana - )}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.LED]: { - NEXT: '', - ARRIVING: '', - }, - [APP_THEME.JR_KYUSHU]: { - NEXT: `${ - firstSpeech ? `このバスは${boundForJa}行きです。` : '' - }次は${nextStation?.groupId === selectedBound?.groupId && !isLoopLine ? '終点、' : ''}${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}、${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}。`, - ARRIVING: `まもなく、${nextStation?.groupId === selectedBound?.groupId && !isLoopLine ? '終点、' : ''}${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}、${replaceJapaneseText( - nextStation?.name, - nextStation?.nameKatakana - )}。${ - nextStation?.groupId === selectedBound?.groupId && !isLoopLine - ? `${currentLine.nameShort}をご利用くださいまして、ありがとうございました。` - : '' - }`, - }, - [APP_THEME.ODAKYU]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.E231]: { NEXT: '', ARRIVING: '' }, - }; - return map; - }, [ - afterNextStation, - allStops, - betweenNextStation, - boundForJa, - currentLine, - firstSpeech, - isAfterNextStopTerminus, - isLoopLine, - isNextStopTerminus, - nextStation?.name, - nextStation?.nameKatakana, - replaceJapaneseText, - selectedBound, - viaStation, - nextStation?.groupId, - selectedBound?.groupId, - ]); - - const englishTemplate: Record | null = - useMemo(() => { - if (!currentLine || !selectedBound) { - return EMPTY_TTS_TEXT; - } - - const map = { - [APP_THEME.TOKYO_METRO]: { - NEXT: `The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.${ - firstSpeech - ? ` This bus is on the ${ - station?.line?.company?.nameEnglishShort ?? '' - } bound for ${boundForEn}. ${ - afterNextStation - ? `The next stop after ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${`, is ${ph( - afterNextStation?.nameTtsSegments, - afterNextStation?.nameRoman - )}${isAfterNextStopTerminus ? ' terminal' : ''}`}.` - : '' - }${ - betweenNextStation.length - ? ' For stations in between, Please change buses at the next stop.' - : '' - }` - : '' - }`, - ARRIVING: `Arriving at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', the last stop.' : '.' - }${ - isNextStopTerminus - ? ` Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}.` - : '' - }`, - }, - [APP_THEME.TY]: { - NEXT: `${ - firstSpeech - ? `Thank you for using the ${ - station?.line?.company?.nameEnglishShort ?? '' - }. This bus is bound for ${boundForEn}. ` - : '' - }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', the last stop.' : '.' - }`, - ARRIVING: `We will soon make a brief stop at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', the last stop.' : '.' - }${ - afterNextStation - ? ` The stop after ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}, will be ${ph( - afterNextStation.nameTtsSegments, - afterNextStation.nameRoman - )}${isAfterNextStopTerminus ? ' the last stop' : ''}.` - : '' - }${ - isNextStopTerminus - ? ` Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}.` - : '' - }`, - }, - [APP_THEME.YAMANOTE]: { - NEXT: `${ - firstSpeech - ? `This is the ${station?.line?.company?.nameEnglishShort ?? ''} bus bound for ${boundForEn}. ` - : '' - }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.`, - ARRIVING: `The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', terminal.' : '.' - }${ - isNextStopTerminus - ? ' Thank you for traveling with us, and look forward to serving you again.' - : '' - }`, - }, - [APP_THEME.JO]: { - NEXT: '', - ARRIVING: '', - }, - [APP_THEME.JL]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.SAIKYO]: { - NEXT: `${ - firstSpeech - ? `This is the ${station?.line?.company?.nameEnglishShort ?? ''} bus bound for ${boundForEn}. ` - : '' - }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', terminal.' : '.' - }`, - ARRIVING: `The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - isNextStopTerminus ? ', terminal.' : '.' - }${ - isNextStopTerminus - ? ' Thank you for traveling with us, and look forward to serving you again.' - : '' - }`, - }, - [APP_THEME.JR_WEST]: { - NEXT: `${ - firstSpeech - ? `Thank you for using ${currentLine?.company?.nameEnglishShort}. This bus is bound for ${boundForEn} ${ - viaStation - ? `via ${ph(viaStation.nameTtsSegments, viaStation.nameRoman)}` - : '' - }. We will be stopping at ${allStops - .slice(0, 5) - .map((s) => - s.id === selectedBound?.id && !isLoopLine - ? `${ph(s.nameTtsSegments, s.nameRoman)} terminal` - : `${ph(s.nameTtsSegments, s.nameRoman)}` - ) - .join(', ')}. ${ - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.id === selectedBound?.id - ? '' - : `Stops after ${ph( - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.nameTtsSegments, - allStops - .slice(0, 5) - .filter((s) => s) - .reverse()[0]?.nameRoman - )} will be announced later. ` - }` - : '' - }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - nextStation?.groupId === selectedBound?.groupId && !isLoopLine - ? ' terminal.' - : '.' - }`, - ARRIVING: `We will soon be making a brief stop at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.${ - afterNextStation - ? `After leaving ${ph( - nextStation?.nameTtsSegments, - nextStation?.nameRoman - )}, We will be stopping at ${ph(afterNextStation.nameTtsSegments, afterNextStation.nameRoman)}.` - : '' - }`, - }, - [APP_THEME.TOEI]: { - NEXT: `${ - firstSpeech - ? `Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}. This bus is bound for ${boundForEn}. ` - : '' - }The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.`, - ARRIVING: `We will soon be arriving at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}.${ - afterNextStation - ? ` The stop after ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}, will be ${ph( - afterNextStation.nameTtsSegments, - afterNextStation.nameRoman - )}${isAfterNextStopTerminus ? ' the last stop' : ''}.` - : '' - }${ - isNextStopTerminus - ? ` Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}.` - : '' - }`, - }, - [APP_THEME.LED]: { - NEXT: '', - ARRIVING: '', - }, - [APP_THEME.JR_KYUSHU]: { - NEXT: `${firstSpeech ? `This bus is bound for ${boundForEn}.` : ''} The next stop is ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - nextStation?.groupId === selectedBound?.groupId && !isLoopLine - ? ' terminal.' - : '.' - }`, - ARRIVING: `We will soon be arriving at ${ph(nextStation?.nameTtsSegments, nextStation?.nameRoman)}${ - nextStation?.groupId === selectedBound?.groupId && !isLoopLine - ? ' terminal.' - : '.' - }${ - nextStation?.groupId === selectedBound?.groupId && !isLoopLine - ? ` Thank you for using the ${station?.line?.company?.nameEnglishShort ?? ''}.` - : '' - }`, - }, - [APP_THEME.ODAKYU]: { NEXT: '', ARRIVING: '' }, - [APP_THEME.E231]: { NEXT: '', ARRIVING: '' }, - }; - return map; - }, [ - afterNextStation, - allStops, - betweenNextStation.length, - boundForEn, - currentLine, - firstSpeech, - isAfterNextStopTerminus, - isLoopLine, - isNextStopTerminus, - nextStation?.groupId, - selectedBound?.groupId, - nextStation?.nameTtsSegments, - selectedBound, - viaStation, - station?.line?.company?.nameEnglishShort, - nextStation?.nameRoman, - ]); - - const resolved = resolveTemplateTheme(theme); - - const jaText = useMemo( - () => japaneseTemplate?.[resolved]?.[stoppingState] ?? '', - [japaneseTemplate, resolved, stoppingState] - ); - - const enText = useMemo( - () => englishTemplate?.[resolved]?.[stoppingState] ?? '', - [englishTemplate, resolved, stoppingState] - ); - - const nextJaText = useMemo( - () => japaneseTemplate?.[resolved]?.NEXT ?? '', - [japaneseTemplate, resolved] - ); - - const nextEnText = useMemo( - () => englishTemplate?.[resolved]?.NEXT ?? '', - [englishTemplate, resolved] - ); - - if (!enabled) { - return { text: [], nextText: [] }; - } - - return { - text: [ - jaText.trim().replace(parenthesisRegexp, ''), - enText.trim().replace(parenthesisRegexp, ''), - ], - nextText: [ - nextJaText.trim().replace(parenthesisRegexp, ''), - nextEnText.trim().replace(parenthesisRegexp, ''), - ], - }; -}; diff --git a/src/hooks/useTTS.test.ts b/src/hooks/useTTS.test.ts index 19c04f71e..b5833243d 100644 --- a/src/hooks/useTTS.test.ts +++ b/src/hooks/useTTS.test.ts @@ -29,13 +29,6 @@ jest.mock('./useTTSText', () => ({ })), })); -jest.mock('./useBusTTSText', () => ({ - useBusTTSText: jest.fn(() => ({ - text: ['ja text', 'en text'], - nextText: ['ja next', 'en next'], - })), -})); - jest.mock('./usePrevious', () => ({ usePrevious: jest.fn(() => ['', '']), })); diff --git a/src/hooks/useTTS.ts b/src/hooks/useTTS.ts index 4e08130e2..61da5f2cc 100644 --- a/src/hooks/useTTS.ts +++ b/src/hooks/useTTS.ts @@ -18,7 +18,6 @@ import { safeRemovePlayer, } from '../utils/ttsAudioPlayer'; import { fetchSpeechAudio } from '../utils/ttsSpeechFetcher'; -import { useBusTTSText } from './useBusTTSText'; import { useCachedInitAnonymousUser } from './useCachedAnonymousUser'; import { useCurrentLine } from './useCurrentLine'; import { usePrevious } from './usePrevious'; @@ -64,12 +63,8 @@ export const useTTS = (): void => { const speechWithTextRef = useRef< ((ja: string, en: string) => Promise) | null >(null); - const trainTTSResult = useTTSText(firstSpeechRef.current, enabled); - const busTTSResult = useBusTTSText(firstSpeechRef.current, enabled); - const ttsResult = - currentLine?.transportType === TransportType.Bus - ? busTTSResult - : trainTTSResult; + const isBus = currentLine?.transportType === TransportType.Bus; + const ttsResult = useTTSText(firstSpeechRef.current, enabled, isBus); const ttsText = ttsResult.text; const prefetchText = ttsResult.nextText; const [prevTextJa, prevTextEn] = usePrevious(ttsText); diff --git a/src/hooks/useBusTTSText.test.tsx b/src/hooks/useTTSText.bus.test.tsx similarity index 97% rename from src/hooks/useBusTTSText.test.tsx rename to src/hooks/useTTSText.bus.test.tsx index 763355e09..084b26fdb 100644 --- a/src/hooks/useBusTTSText.test.tsx +++ b/src/hooks/useTTSText.bus.test.tsx @@ -5,8 +5,8 @@ import { useEffect } from 'react'; import type { Station } from '~/@types/graphql'; import { TOEI_SHINJUKU_LINE_LOCAL } from '~/__fixtures__/line'; import { TOEI_SHINJUKU_LINE_STATIONS } from '~/__fixtures__/station'; -import { useBusTTSText } from '~/hooks/useBusTTSText'; import { useNextStation } from '~/hooks/useNextStation'; +import { useTTSText } from '~/hooks/useTTSText'; import type { LineDirection } from '~/models/Bound'; import type { HeaderStoppingState } from '~/models/HeaderTransitionState'; import type { AppTheme } from '~/models/Theme'; @@ -20,6 +20,8 @@ jest.mock('~/hooks/useNextStation', () => ({ useNextStation: jest.fn(), })); +// バスモード (useTTSText の isBus=true) の TTS テキスト生成テスト。 +// 旧 useBusTTSText を useTTSText に統合した後の回帰確認を兼ねる。 const useBusTTSTextWithJotai = ( theme: AppTheme, headerState: HeaderStoppingState, @@ -52,7 +54,7 @@ const useBusTTSTextWithJotai = ( setLineState((prev) => ({ ...prev, selectedLine })); }, [headerState, setLineState, setStationState, theme]); - const texts = useBusTTSText(firstSpeech, true); + const texts = useTTSText(firstSpeech, true, true); return texts; }; @@ -63,7 +65,7 @@ const wrapper = ({ children }: { children: React.ReactNode }) => ( const setupMockUseNextStation = (station: Station) => (useNextStation as jest.Mock).mockReturnValue(station); -describe('useBusTTSText', () => { +describe('useTTSText (bus mode)', () => { beforeEach(() => { jest.resetModules(); setupMockUseNextStation(TOEI_SHINJUKU_LINE_STATIONS[1]); @@ -104,7 +106,7 @@ describe('useBusTTSText', () => { setLineState((prev) => ({ ...prev, selectedLine })); }, [setLineState, setStationState]); - return useBusTTSText(true, false); + return useTTSText(true, false, true); }, { wrapper: wrapper, diff --git a/src/hooks/useTTSText.ts b/src/hooks/useTTSText.ts index 2d3562646..63dcfcbca 100644 --- a/src/hooks/useTTSText.ts +++ b/src/hooks/useTTSText.ts @@ -54,7 +54,8 @@ const resolveTemplateTheme = (theme: AppTheme): AppTheme => { export const useTTSText = ( firstSpeech = true, - enabled = false + enabled = false, + isBus = false ): TTSTextResult => { const theme = useAtomValue(themeAtom); @@ -66,8 +67,14 @@ export const useTTSText = ( const station = useCurrentStation(); const currentLineOrigin = useCurrentLine(); - const connectedLines = useConnectedLines(); - const transferLines = useTransferLines(); + const connectedLinesOrigin = useConnectedLines(); + const transferLinesOrigin = useTransferLines(); + // バスは直通・乗換・列車種別・駅ナンバリングを案内しない。 + // フックのルートで配列を空にしておけば、以降の dedupe / format / 番号計算は + // すべて空入力に対する no-op となり、テンプレ側の hasTransferLines などの + // 真偽フラグも自動で false になる。 + const connectedLines = isBus ? [] : connectedLinesOrigin; + const transferLines = isBus ? [] : transferLinesOrigin; // 博多駅の鹿児島本線のように、データ上は方面別に同じ路線名が // 別レコードで登録されているケースがある。表示用には両方残したいが、 // TTS では同じ路線名を続けて読み上げないように重複を除外する。 @@ -75,7 +82,8 @@ export const useTTSText = ( () => dedupeLinesByTtsName(transferLines), [transferLines] ); - const currentTrainTypeOrigin = useCurrentTrainType(); + const currentTrainTypeFromHook = useCurrentTrainType(); + const currentTrainTypeOrigin = isBus ? null : currentTrainTypeFromHook; const loopLineBoundJa = useLoopLineBound(false, 'JA'); const loopLineBoundEn = useLoopLineBound(false, 'EN'); const { directionalStops } = useBounds(stations); @@ -87,6 +95,7 @@ export const useTTSText = ( const getStationNumberIndex = useStationNumberIndexFunc(); const nextStationNumber = useMemo(() => { + if (isBus) return; if (!nextStationOrigin) return; if (!nextStationOrigin.stationNumbers) return; @@ -102,7 +111,7 @@ export const useTTSText = ( } return nextStationOrigin.stationNumbers[stationNumberIndex]; - }, [getStationNumberIndex, nextStationOrigin]); + }, [getStationNumberIndex, nextStationOrigin, isBus]); const currentLine = currentLineOrigin ?? null; @@ -130,13 +139,13 @@ export const useTTSText = ( ); const yamanoteTrainTypeJa = useMemo(() => { - if (!isYamanoteLine || !selectedDirection) return null; + if (isBus || !isYamanoteLine || !selectedDirection) return null; return selectedDirection === 'INBOUND' ? 'やまのて線内回り' : 'やまのて線外回り'; - }, [isYamanoteLine, selectedDirection]); + }, [isBus, isYamanoteLine, selectedDirection]); - const yamanoteTrainTypeEn = isYamanoteLine ? 'Yamanote Line' : null; + const yamanoteTrainTypeEn = !isBus && isYamanoteLine ? 'Yamanote Line' : null; // directionalStops のカッコ書きも TTS で読み上げないため、 // boundForJa / boundForEn に渡す前に取り除く (issue #1175)。 @@ -321,6 +330,22 @@ export const useTTSText = ( const isBoundStop = (s: Station): boolean => s.groupId === selectedBound?.groupId && !isLoopLine; + // バスは元アナウンス上「列車種別」概念を持たないので、 + // テンプレ側の `{#if hasTrainType}` で 各駅停車/Local Service 等の挿入を抑止する。 + // 鉄道時は常に true (列車種別未設定でも `各駅停車` / `Local` にフォールバックする既存挙動を維持)。 + const hasTrainType = !isBus; + // 鉄道は列車種別が無いと「次の停車駅」案内を出さない (各駅停車では自明)。 + // バスは元アナウンスが常に「次の停車駅」を案内するため、列車種別の有無を問わず + // afterNextStation があれば announce する。 + const shouldAnnounceAfterNextStop = isBus + ? !!afterNextStation + : !!(currentTrainType && afterNextStation); + // TOEI 等で `{vehicleJa}は、…{boundForJa}ゆきです` の塊を出すかどうか。 + // 鉄道は停車のたびに繰り返し読み上げる (常に true)。 + // バスは初回放送のみ (実機の都営バスがそういう挙動なので、PR #5990 で + // バス版だけ明示的に firstSpeech ゲートを入れていた)。 + const announceVehicleSummary = !isBus || firstSpeech; + return { // フラグ firstSpeech, @@ -330,7 +355,9 @@ export const useTTSText = ( hasConnectedLines: connectedLines.length > 0, hasBetweenStations: betweenNextStation.length > 0, hasAfterNextStation: !!afterNextStation, - hasTrainTypeAndAfterNext: !!(currentTrainType && afterNextStation), + hasTrainType, + shouldAnnounceAfterNextStop, + announceVehicleSummary, hasViaStation: !!viaStation, nextStationIsBound: nextStation?.groupId === selectedBound?.groupId && !isLoopLine, @@ -341,6 +368,20 @@ export const useTTSText = ( hasNextStationNumberText: nextStationNumberText.length > 0, yamanoteTrainTypeEn: yamanoteTrainTypeEn ?? '', + // 乗り物名 (アナウンスで `この電車` / `この列車` / `このバス` をテーマと + // モードで切り替える)。JR_KYUSHU の鉄道のみ歴史的に `この列車` を使うため + // 個別分岐させる。 + vehicleJa: isBus + ? 'このバス' + : theme === APP_THEME.JR_KYUSHU + ? 'この列車' + : 'この電車', + vehicleEn: isBus ? 'bus' : 'train', + vehicleEnPlural: isBus ? 'buses' : 'trains', + // 「次の停車場」を表す語。バスアナウンスは慣習的に "stop" を用いる。 + // 鉄道は既存表記 "station" を維持。 + placeEn: isBus ? 'stop' : 'station', + // 日本語 nextStationJa: replaceJapaneseText( nextStation?.name, @@ -410,6 +451,12 @@ export const useTTSText = ( nextStationEn: ph(nextStation?.nameTtsSegments, nextStation?.nameRoman), currentLineEn: ph(currentLine.nameTtsSegments, currentLine.nameRoman), currentLineCompanyEn: currentLine.company?.nameEnglishShort ?? '', + // "Thank you for using the X" に差し込む語。鉄道は路線名 (e.g. "Tokyo Metro Tozai Line")、 + // バスは路線レコードに英語名が無いケースが多いので会社名 (e.g. "Toei Bus") を使う。 + // 旧 useBusTTSText が `station.line.company.nameEnglishShort` を採用していた挙動に揃える。 + currentLineThanksEn: isBus + ? (currentLine.company?.nameEnglishShort ?? '') + : ph(currentLine.nameTtsSegments, currentLine.nameRoman), boundForEn, // 多くのテーマで使う共通形 (yamanote ?? phoneme || 'Local') trainTypeEn: @@ -446,6 +493,7 @@ export const useTTSText = ( currentTrainType, firstSpeech, isAfterNextStopTerminus, + isBus, isLoopLine, isNextStopTerminus, lastAnnouncedStop, @@ -454,6 +502,7 @@ export const useTTSText = ( nextStationNumberText, selectedBound, shouldAnnounceJrWestStopList, + theme, ttsTransferLines, viaStation, yamanoteTrainTypeEn, From c457f88b0757650d908dc92ab4a70dd3606dff76 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 09:58:05 +0900 Subject: [PATCH 7/8] Bump version for canary release (#5999) Co-authored-by: TinyKitten <32848922+TinyKitten@users.noreply.github.com> --- android/app/build.gradle | 4 +-- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a351d0267..bac4ee183 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -141,12 +141,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000421 + versionCode 100000422 versionName "10.5.0" } prod { dimension "environment" - versionCode 100000421 + versionCode 100000422 versionName "10.5.0" } } diff --git a/app.config.ts b/app.config.ts index d1ea25547..d66ceb4e1 100644 --- a/app.config.ts +++ b/app.config.ts @@ -53,7 +53,7 @@ export default ({ config }: ConfigContext) => ({ }, }, ios: { - buildNumber: '2634', + buildNumber: '2635', bundleIdentifier: process.env.EAS_BUILD_PROFILE === 'production' ? 'me.tinykitten.trainlcd' @@ -70,7 +70,7 @@ export default ({ config }: ConfigContext) => ({ ? 'me.tinykitten.trainlcd' : 'me.tinykitten.trainlcd.dev', permissions: [], - versionCode: 100000421, + versionCode: 100000422, }, owner: 'trainlcd', }); diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 2855a0eec..f6e6b08ed 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2419,7 +2419,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2458,7 +2458,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2517,7 +2517,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2623,7 +2623,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2702,7 +2702,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2741,7 +2741,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2952,7 +2952,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3003,7 +3003,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3054,7 +3054,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3112,7 +3112,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3163,7 +3163,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3220,7 +3220,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3268,7 +3268,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3319,7 +3319,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3538,7 +3538,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3594,7 +3594,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3644,7 +3644,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3702,7 +3702,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2634; + CURRENT_PROJECT_VERSION = 2635; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From f1a8a291cca772efd7dab65dae2ee318cb5af857 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Fri, 15 May 2026 10:17:12 +0900 Subject: [PATCH 8/8] =?UTF-8?q?v10.5.1=20=E3=82=92=E3=83=AA=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 8 ++--- app.config.ts | 6 ++-- ios/TrainLCD.xcodeproj/project.pbxproj | 44 +++++++++++++------------- package-lock.json | 4 +-- package.json | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bac4ee183..6e060711f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -141,13 +141,13 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000422 - versionName "10.5.0" + versionCode 100000423 + versionName "10.5.1" } prod { dimension "environment" - versionCode 100000422 - versionName "10.5.0" + versionCode 100000423 + versionName "10.5.1" } } } diff --git a/app.config.ts b/app.config.ts index d66ceb4e1..4c46c4127 100644 --- a/app.config.ts +++ b/app.config.ts @@ -3,7 +3,7 @@ import type { ConfigContext } from 'expo/config'; export default ({ config }: ConfigContext) => ({ name: 'TrainLCD', slug: 'trainlcd', - version: '10.5.0', + version: '10.5.1', plugins: [ 'expo-image', 'expo-font', @@ -53,7 +53,7 @@ export default ({ config }: ConfigContext) => ({ }, }, ios: { - buildNumber: '2635', + buildNumber: '2636', bundleIdentifier: process.env.EAS_BUILD_PROFILE === 'production' ? 'me.tinykitten.trainlcd' @@ -70,7 +70,7 @@ export default ({ config }: ConfigContext) => ({ ? 'me.tinykitten.trainlcd' : 'me.tinykitten.trainlcd.dev', permissions: [], - versionCode: 100000422, + versionCode: 100000423, }, owner: 'trainlcd', }); diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index f6e6b08ed..b94067706 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2419,7 +2419,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2458,7 +2458,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2517,7 +2517,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2573,7 +2573,7 @@ "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(inherited)\"", ); - MARKETING_VERSION = 10.5.0; + MARKETING_VERSION = 10.5.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; @@ -2623,7 +2623,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2675,7 +2675,7 @@ "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(inherited)\"", ); - MARKETING_VERSION = 10.5.0; + MARKETING_VERSION = 10.5.1; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; @@ -2702,7 +2702,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2741,7 +2741,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2952,7 +2952,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3003,7 +3003,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3054,7 +3054,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3112,7 +3112,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3163,7 +3163,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3220,7 +3220,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3268,7 +3268,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3319,7 +3319,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3538,7 +3538,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3594,7 +3594,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3644,7 +3644,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3668,7 +3668,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 10.5.0; + MARKETING_VERSION = 10.5.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3702,7 +3702,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2635; + CURRENT_PROJECT_VERSION = 2636; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3722,7 +3722,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 10.5.0; + MARKETING_VERSION = 10.5.1; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PODS_ROOT = "${SRCROOT}/Pods"; diff --git a/package-lock.json b/package-lock.json index 266cc53b1..1584ab6a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trainlcd", - "version": "10.5.0", + "version": "10.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trainlcd", - "version": "10.5.0", + "version": "10.5.1", "hasInstallScript": true, "dependencies": { "@apollo/client": "^4.0.7", diff --git a/package.json b/package.json index 6f06cc5ba..878e42901 100644 --- a/package.json +++ b/package.json @@ -168,5 +168,5 @@ } }, "name": "trainlcd", - "version": "10.5.0" + "version": "10.5.1" }