From 7da4b9a54d7398957dd2e4b9131edf3e187cdab9 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Sun, 4 Oct 2026 23:11:25 +0900 Subject: [PATCH 01/17] =?UTF-8?q?=E5=88=B0=E7=9D=80=E5=9C=8F=E3=82=92?= =?UTF-8?q?=E5=8F=96=E3=82=8A=E3=81=93=E3=81=BC=E3=81=97=E3=81=9F=E9=80=9A?= =?UTF-8?q?=E9=81=8E=E9=A7=85=E3=82=92=E9=80=9A=E3=82=8A=E9=81=8E=E3=81=8E?= =?UTF-8?q?=E3=81=9F=E3=82=89=E7=8F=BE=E5=9C=A8=E9=A7=85=E3=82=92=E3=81=9D?= =?UTF-8?q?=E3=81=AE=E9=A7=85=E3=81=BE=E3=81=A7=E9=80=B2=E3=82=81=E3=82=8B?= =?UTF-8?q?=20(#7167)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePassedStation.test.tsx | 86 ++++++++++++++ src/hooks/usePassedStation.ts | 59 ++++++++++ src/hooks/useRefreshStation.test.tsx | 80 +++++++++++++ src/hooks/useRefreshStation.ts | 29 ++++- src/utils/passedStation.gpx.test.ts | 162 +++++++++++++++++++++++++++ src/utils/passedStation.test.ts | 102 +++++++++++++++++ src/utils/passedStation.ts | 113 +++++++++++++++++++ 7 files changed, 626 insertions(+), 5 deletions(-) create mode 100644 src/hooks/usePassedStation.test.tsx create mode 100644 src/hooks/usePassedStation.ts create mode 100644 src/utils/passedStation.gpx.test.ts create mode 100644 src/utils/passedStation.test.ts create mode 100644 src/utils/passedStation.ts diff --git a/src/hooks/usePassedStation.test.tsx b/src/hooks/usePassedStation.test.tsx new file mode 100644 index 0000000000..7a4c31b2a9 --- /dev/null +++ b/src/hooks/usePassedStation.test.tsx @@ -0,0 +1,86 @@ +import { renderHook } from '@testing-library/react-native'; +import { createStore, Provider } from 'jotai'; +import type React from 'react'; +import { StopCondition } from '~/@types/graphql'; +import { locationAtom } from '~/store/atoms/location'; +import { + selectedDirectionAtom, + stationAtom, + stationsAtom, +} from '~/store/atoms/station'; +import { createStation } from '~/utils/test/factories'; +import { useLoopLine } from './useLoopLine'; +import { usePassedStation } from './usePassedStation'; + +jest.mock('./useLoopLine', () => ({ + useLoopLine: jest.fn(), +})); + +const mockUseLoopLine = useLoopLine as jest.MockedFunction; + +// 東海道新幹線は東京起点の並び。三島→新横浜は上り(OUTBOUNDで逆順に辿る)。 +const shinYokohama = createStation(100203, { + latitude: 35.5069, + longitude: 139.6175, +}); +const odawara = createStation(100204, { + latitude: 35.2564, + longitude: 139.1552, + stopCondition: StopCondition.Not, +}); +const atami = createStation(100205, { + latitude: 35.1036, + longitude: 139.0779, + stopCondition: StopCondition.Not, +}); +const mishima = createStation(100206, { + latitude: 35.1265, + longitude: 138.9108, +}); + +const setup = ( + direction: 'INBOUND' | 'OUTBOUND', + isLoopLine = false +): ReturnType => { + mockUseLoopLine.mockReturnValue({ + isLoopLine, + } as ReturnType); + const store = createStore(); + store.set(stationsAtom, [shinYokohama, odawara, atami, mishima]); + store.set(selectedDirectionAtom, direction); + store.set(stationAtom, mishima); + store.set(locationAtom, { + timestamp: 0, + coords: { + latitude: 35.40113, + longitude: 139.39607, + accuracy: 7, + altitude: null, + altitudeAccuracy: null, + heading: null, + speed: 80, + }, + }); + const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} + ); + return renderHook(() => usePassedStation(), { wrapper }).result.current; +}; + +describe('usePassedStation', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('進行方向順に並べ直して、通り過ぎた通過駅を返す', () => { + expect(setup('OUTBOUND')?.id).toBe(odawara.id); + }); + + it('逆方向に乗っている扱いなら現在駅の先に区間が無く、何も返さない', () => { + expect(setup('INBOUND')).toBeUndefined(); + }); + + it('ループ線では何も返さない', () => { + expect(setup('OUTBOUND', true)).toBeUndefined(); + }); +}); diff --git a/src/hooks/usePassedStation.ts b/src/hooks/usePassedStation.ts new file mode 100644 index 0000000000..3d3d03000f --- /dev/null +++ b/src/hooks/usePassedStation.ts @@ -0,0 +1,59 @@ +import { useAtomValue } from 'jotai'; +import { useMemo } from 'react'; +import type { Station } from '~/@types/graphql'; +import { locationAtom } from '~/store/atoms/location'; +import dropEitherJunctionStation from '~/utils/dropJunctionStation'; +import { findPassedStation } from '~/utils/passedStation'; +import reverseStations from '~/utils/reverseStations'; +import { + selectedDirectionAtom, + stationAtom, + stationsAtom, +} from '../store/atoms/station'; +import { useLoopLine } from './useLoopLine'; + +/** + * 到着圏を取りこぼした通過駅のうち、現在地がすでに通り過ぎたものを返す。 + * useRefreshStation が現在駅を後方に取り残さないための自己修復に使う。 + * + * ループ線は対象外にする。進行方向順の並びが終端で折り返すため、 + * 区間の選び方が非ループ線と同じにならない。 + */ +export const usePassedStation = (): Station | undefined => { + const location = useAtomValue(locationAtom); + const stationsFromState = useAtomValue(stationsAtom); + const selectedDirection = useAtomValue(selectedDirectionAtom); + const currentStation = useAtomValue(stationAtom); + const { isLoopLine } = useLoopLine(); + + const latitude = location?.coords.latitude; + const longitude = location?.coords.longitude; + const accuracy = location?.coords.accuracy; + + const orderedStations = useMemo(() => { + if (selectedDirection == null || isLoopLine) { + return null; + } + const stations = dropEitherJunctionStation( + stationsFromState, + selectedDirection + ); + return selectedDirection === 'INBOUND' + ? stations + : reverseStations(stations); + }, [isLoopLine, selectedDirection, stationsFromState]); + + return useMemo( + () => + orderedStations + ? findPassedStation( + orderedStations, + currentStation, + latitude, + longitude, + accuracy + ) + : undefined, + [accuracy, currentStation, latitude, longitude, orderedStations] + ); +}; diff --git a/src/hooks/useRefreshStation.test.tsx b/src/hooks/useRefreshStation.test.tsx index 10806dbecb..9543612293 100644 --- a/src/hooks/useRefreshStation.test.tsx +++ b/src/hooks/useRefreshStation.test.tsx @@ -6,6 +6,7 @@ import * as useApproachingStationModule from '~/hooks/useApproachingStation'; import * as useCanGoForwardModule from '~/hooks/useCanGoForward'; import * as useNearestStationModule from '~/hooks/useNearestStation'; import * as useNextStationModule from '~/hooks/useNextStation'; +import * as usePassedStationModule from '~/hooks/usePassedStation'; import { useRefreshStation } from '~/hooks/useRefreshStation'; import * as useThresholdModule from '~/hooks/useThreshold'; import * as useWrongDirectionDetectorModule from '~/hooks/useWrongDirectionDetector'; @@ -85,6 +86,9 @@ describe('useRefreshStation', () => { jest .spyOn(useApproachingStationModule, 'useApproachingStation') .mockReturnValue(mockStation); + jest + .spyOn(usePassedStationModule, 'usePassedStation') + .mockReturnValue(undefined); }); afterEach(() => { @@ -395,4 +399,80 @@ describe('useRefreshStation', () => { const { nextState } = setupEtaDwelling('AT_STATION'); expect(nextState.arrived).toBe(false); }); + + describe('通過駅の取りこぼしからの自己修復', () => { + const passedStation = { + ...mockStation, + id: 40, + groupId: 40, + latitude: 35.2, + longitude: 135.0, + stopCondition: StopCondition.Not, + }; + const stalePrevStation = { ...mockStation, id: 50, groupId: 50 }; + + const renderWithLocation = (latitude: number) => { + mockUseAtomValue + .mockReturnValueOnce({ + coords: { latitude, longitude: 135.0 }, + }) // locationAtom + .mockReturnValue({ targetStationIds: [] }); // notifyState + + const setStation = jest.fn(); + const setNavigation = jest.fn(); + mockUseSetAtom + .mockReturnValueOnce(setStation) + .mockReturnValueOnce(setNavigation) + .mockReturnValue(jest.fn()); + + jest + .spyOn(useNearestStationModule, 'useNearestStation') + .mockReturnValue(mockStation); + jest + .spyOn(useNextStationModule, 'useNextStation') + .mockReturnValue(mockStation); + jest + .spyOn(usePassedStationModule, 'usePassedStation') + .mockReturnValue(passedStation); + jest + .spyOn(useCanGoForwardModule, 'useCanGoForward') + .mockReturnValue(true); + jest.spyOn(useThresholdModule, 'useThreshold').mockReturnValue({ + arrivedThreshold: 100, + approachingThreshold: 300, + }); + jest + .spyOn(useWrongDirectionDetectorModule, 'useWrongDirectionDetector') + .mockReturnValue({ + isWrongDirection: false, + isLoopLineWrongDirection: false, + }); + + renderHook(() => useRefreshStation(), { + wrapper: ({ children }) => {children}, + }); + + expect(setStation).toHaveBeenCalled(); + const updater = setStation.mock.calls[0][0] as (prev: any) => any; + return { + nextState: updater({ station: stalePrevStation }), + setNavigation, + }; + }; + + it('到着していなければ、通り過ぎた通過駅へ現在駅を進める(到着は扱わない)', () => { + // 最寄り駅(35.0,135.0)から約55km北。到着圏の外 + const { nextState, setNavigation } = renderWithLocation(35.5); + expect(nextState.station).toBe(passedStation); + expect(nextState.arrived).toBe(false); + // ヘッダーの駅(停車駅)は動かさない + expect(setNavigation).not.toHaveBeenCalled(); + }); + + it('到着中は到着した最寄り駅が優先される', () => { + const { nextState } = renderWithLocation(35.0); + expect(nextState.station).toBe(mockStation); + expect(nextState.arrived).toBe(true); + }); + }); }); diff --git a/src/hooks/useRefreshStation.ts b/src/hooks/useRefreshStation.ts index a2c1b18d8d..57009a5e48 100644 --- a/src/hooks/useRefreshStation.ts +++ b/src/hooks/useRefreshStation.ts @@ -16,6 +16,7 @@ import { useApproachingStation } from './useApproachingStation'; import { useCanGoForward } from './useCanGoForward'; import { useNearestStation } from './useNearestStation'; import { useNextStation } from './useNextStation'; +import { usePassedStation } from './usePassedStation'; import { useStationNumberIndexFunc } from './useStationNumberIndexFunc'; import { useThreshold } from './useThreshold'; import { useWrongDirectionDetector } from './useWrongDirectionDetector'; @@ -53,6 +54,8 @@ export const useRefreshStation = (): void => { useAtomValue(notifyState); const nearestStation = useNearestStation(); + // 到着圏を取りこぼしたまま通り過ぎた通過駅。現在駅が後方に取り残されたときの自己修復に使う + const passedStation = usePassedStation(); const canGoForward = useCanGoForward(); const getStationNumberIndex = useStationNumberIndexFunc(); const { arrivedThreshold, approachingThreshold } = useThreshold(); @@ -239,10 +242,19 @@ export const useRefreshStation = (): void => { setStation((prev) => { const approaching = !isArrived && !getIsPass(nearestStation) && isApproaching; - const station = - isArrived && prev.station?.id !== nearestStation.id - ? nearestStation - : prev.station; + // 到着していない間に、到着圏を取りこぼした通過駅を通り過ぎていたら現在駅を進める。 + // 到着は扱わない(arrivedはfalseのまま)ので、到着通知・ETAの基準駅は動かない。 + const station = (() => { + if (isArrived) { + return prev.station?.id !== nearestStation.id + ? nearestStation + : prev.station; + } + if (passedStation && prev.station?.id !== passedStation.id) { + return passedStation; + } + return prev.station; + })(); if ( prev.approaching === approaching && prev.arrived === isArrived && @@ -260,5 +272,12 @@ export const useRefreshStation = (): void => { : prev ); } - }, [isApproaching, isArrived, nearestStation, setNavigation, setStation]); + }, [ + isApproaching, + isArrived, + nearestStation, + passedStation, + setNavigation, + setStation, + ]); }; diff --git a/src/utils/passedStation.gpx.test.ts b/src/utils/passedStation.gpx.test.ts new file mode 100644 index 0000000000..f8e77e9747 --- /dev/null +++ b/src/utils/passedStation.gpx.test.ts @@ -0,0 +1,162 @@ +import { type Station, StopCondition } from '~/@types/graphql'; +import { ARRIVED_MAX_THRESHOLD } from '~/constants'; +import { getAccuracyBonus } from './accuracyBonus'; +import { findPassedStation } from './passedStation'; +import { createStation } from './test/factories'; +import { + METERS_PER_DEG_LAT, + makeNoise, + parseGpx, + type TrackPoint, + trackDistance, + truthAt, +} from './test/gpxTrack'; + +// 東北新幹線 盛岡→仙台(最高320km/h)。一ノ関だけに停車し、5駅を通過する。 +// 駅の座標はGPXの軌跡上で各駅に最も近い点を使う(GPXは駅の座標を通るよう生成されている)。 +const STATIONS: { name: string; index: number; pass: boolean }[] = [ + { name: '盛岡', index: 0, pass: false }, + { name: '新花巻', index: 416, pass: true }, + { name: '北上', index: 579, pass: true }, + { name: '水沢江刺', index: 777, pass: true }, + { name: '一ノ関', index: 1159, pass: false }, + { name: 'くりこま高原', index: 1428, pass: true }, + { name: '古川', index: 1673, pass: true }, + { name: '仙台', index: 2131, pass: false }, +]; + +// 背景ではiOSが約10秒ぶんの測位をまとめて届け、index.js はその最後の1点だけを使う +const BATCH_INTERVAL_MS = 10_000; +const ACCURACY = 10; +// 通過駅をこの距離だけ過ぎても現在駅が手前に残っていたら、取りこぼしとみなす +const MAX_LAG_METERS = 2_000; + +const track = parseGpx('assets/gpx/SampleTohokuShinkansen.gpx'); + +const stations: Station[] = STATIONS.map(({ index, pass }, i) => + createStation(i + 1, { + latitude: track[index].lat, + longitude: track[index].lon, + stopCondition: pass ? StopCondition.Not : StopCondition.All, + }) +); + +// 軌跡に沿った累積距離(m) +const cumulative = track.reduce((acc, p, i) => { + acc.push(i === 0 ? 0 : acc[i - 1] + trackDistance(track[i - 1], p)); + return acc; +}, []); + +const alongTrackAt = (t: number): number => { + const i = track.findIndex((p) => p.t > t); + if (i <= 0) { + return i === 0 ? 0 : cumulative[cumulative.length - 1]; + } + const a = track[i - 1]; + const b = track[i]; + return ( + cumulative[i - 1] + + (cumulative[i] - cumulative[i - 1]) * ((t - a.t) / (b.t - a.t)) + ); +}; + +const stationAlongTrack = STATIONS.map(({ index }) => cumulative[index]); +const arrivedRadius = ARRIVED_MAX_THRESHOLD + getAccuracyBonus(ACCURACY); + +/** + * useRefreshStation の現在駅の更新を、測位をまとめて受け取る間隔で再現する。 + * 最寄り駅の到着圏に入ればその駅へ、入らなければ(withPassedStationのとき)通り過ぎた + * 通過駅へ進める。 + */ +const ride = (phaseMs: number, withPassedStation: boolean) => { + const noise = makeNoise(phaseMs + 1); + let current: Station = stations[0]; + const samples: { along: number; currentIndex: number }[] = []; + // 通過駅の推定で現在駅を進めた瞬間の、進めた先の駅と実際の位置 + const advances: { stationIndex: number; along: number }[] = []; + + for ( + let t = track[0].t + phaseMs; + t <= track[track.length - 1].t; + t += BATCH_INTERVAL_MS + ) { + const truth: TrackPoint = truthAt(track, t); + const latitude = + truth.lat + ((noise() - 0.5) * 2 * ACCURACY) / METERS_PER_DEG_LAT; + const longitude = + truth.lon + + ((noise() - 0.5) * 2 * ACCURACY) / + (METERS_PER_DEG_LAT * Math.cos((truth.lat * Math.PI) / 180)); + const position = { lat: latitude, lon: longitude }; + const along = alongTrackAt(t); + + const arrived = stations.find( + (s) => + trackDistance(position, { + lat: s.latitude as number, + lon: s.longitude as number, + }) <= arrivedRadius + ); + if (arrived) { + current = arrived; + } else if (withPassedStation) { + const passed = findPassedStation( + stations, + current, + latitude, + longitude, + ACCURACY + ); + if (passed && passed !== current) { + current = passed; + advances.push({ stationIndex: stations.indexOf(passed), along }); + } + } + + samples.push({ along, currentIndex: stations.indexOf(current) }); + } + return { samples, advances }; +}; + +// 現在地より手前にある駅のうち、MAX_LAG_METERS 以上前に通り過ぎた最後の駅 +const lastClearlyPassedIndex = (along: number): number => + stationAlongTrack.reduce( + (last, d, i) => (d + MAX_LAG_METERS <= along ? i : last), + 0 + ); + +const PHASES = Array.from({ length: 10 }, (_, i) => i * 1_000); + +describe('findPassedStation (SampleTohokuShinkansen.gpx)', () => { + it('前提: 到着判定だけでは、背景の配信間隔で通過駅を取りこぼす', () => { + const lagged = PHASES.filter((phase) => + ride(phase, false).samples.some( + (s) => s.currentIndex < lastClearlyPassedIndex(s.along) + ) + ); + expect(lagged.length).toBeGreaterThan(0); + }); + + it.each(PHASES)( + '配信の位相 %ims: 通過駅を過ぎて2km以内に現在駅が追いつく', + (phase) => { + for (const s of ride(phase, true).samples) { + expect(s.currentIndex).toBeGreaterThanOrEqual( + lastClearlyPassedIndex(s.along) + ); + } + } + ); + + it.each(PHASES)( + '配信の位相 %ims: 通過駅は実際に通り過ぎてから進める', + (phase) => { + const { advances } = ride(phase, true); + expect(advances.length).toBeGreaterThan(0); + for (const a of advances) { + expect(STATIONS[a.stationIndex].pass).toBe(true); + expect(stationAlongTrack[a.stationIndex]).toBeLessThan(a.along); + } + } + ); +}); diff --git a/src/utils/passedStation.test.ts b/src/utils/passedStation.test.ts new file mode 100644 index 0000000000..39567cf466 --- /dev/null +++ b/src/utils/passedStation.test.ts @@ -0,0 +1,102 @@ +import { StopCondition } from '~/@types/graphql'; +import { createStation } from '~/utils/test/factories'; +import { findPassedStation } from './passedStation'; + +// 東海道新幹線 上り(三島→新横浜)。のぞみは熱海・小田原を通過する。 +const mishima = createStation(100206, { + name: '三島', + latitude: 35.1265, + longitude: 138.9108, +}); +const atami = createStation(100205, { + name: '熱海', + latitude: 35.1036, + longitude: 139.0779, + stopCondition: StopCondition.Not, +}); +const odawara = createStation(100204, { + name: '小田原', + latitude: 35.2564, + longitude: 139.1552, + stopCondition: StopCondition.Not, +}); +const shinYokohama = createStation(100203, { + name: '新横浜', + latitude: 35.5069, + longitude: 139.6175, +}); + +const nozomi = [mishima, atami, odawara, shinYokohama]; + +// 診断ダンプ(2026-10-04)の測位。小田原→新横浜の間で、現在駅は三島に残っていた +const BETWEEN_ODAWARA_AND_SHIN_YOKOHAMA = { + latitude: 35.40113, + longitude: 139.39607, +}; + +describe('findPassedStation', () => { + it('通過駅を2つ取りこぼしても、現在地の区間の始点まで進める', () => { + const { latitude, longitude } = BETWEEN_ODAWARA_AND_SHIN_YOKOHAMA; + expect(findPassedStation(nozomi, mishima, latitude, longitude, 7)?.id).toBe( + odawara.id + ); + }); + + it('熱海と小田原の間なら熱海まで進める', () => { + expect(findPassedStation(nozomi, mishima, 35.18, 139.12, 10)?.id).toBe( + atami.id + ); + }); + + it('現在駅と次駅の間にいる間は進めない', () => { + expect( + findPassedStation(nozomi, mishima, 35.115, 139.0, 10) + ).toBeUndefined(); + }); + + it('通過駅の到着圏が取りうる最大半径(350m)の内側では進めない', () => { + // 小田原から新横浜方向へ約300m + expect( + findPassedStation(nozomi, mishima, 35.2575, 139.1582, 5) + ).toBeUndefined(); + }); + + it('測位誤差を差し引くと到着圏に届きうる場合は進めない', () => { + // 小田原から約1.4kmだが、精度1500mでは手前にいる可能性を否定できない + expect( + findPassedStation(nozomi, mishima, 35.262, 139.17, 1500) + ).toBeUndefined(); + }); + + it('停車駅は飛び越えない', () => { + const kodama = [ + mishima, + atami, + { ...odawara, stopCondition: StopCondition.All }, + shinYokohama, + ]; + const { latitude, longitude } = BETWEEN_ODAWARA_AND_SHIN_YOKOHAMA; + expect( + findPassedStation(kodama, mishima, latitude, longitude, 7) + ).toBeUndefined(); + }); + + it('現在駅より後方にいるときは進めない', () => { + expect( + findPassedStation(nozomi, mishima, 35.14, 138.85, 10) + ).toBeUndefined(); + }); + + it('現在駅が並びに無ければ何もしない', () => { + const { latitude, longitude } = BETWEEN_ODAWARA_AND_SHIN_YOKOHAMA; + expect( + findPassedStation( + nozomi, + createStation(1, { latitude: 35, longitude: 139 }), + latitude, + longitude, + 7 + ) + ).toBeUndefined(); + }); +}); diff --git a/src/utils/passedStation.ts b/src/utils/passedStation.ts new file mode 100644 index 0000000000..3fd52dfda9 --- /dev/null +++ b/src/utils/passedStation.ts @@ -0,0 +1,113 @@ +import getDistance from 'geolib/es/getPreciseDistance'; +import type { Station } from '~/@types/graphql'; +import { ARRIVED_MAX_THRESHOLD } from '~/constants'; +import { getAccuracyBonus } from './accuracyBonus'; +import getIsPass from './isPass'; + +// 通過したとみなすために、通過駅から最低限離れていなければならない距離(m)。 +// 到着圏が取りうる最大の半径(ARRIVED_MAX_THRESHOLD + 精度補正の上限)より外に置く。 +// その内側では通常の到着判定がまだ通過駅を拾える。また駅の手前と奥の区別がつかない +// (カーブ上で次区間の方が近く見える)のも駅のごく近くに限られる。 +const PASSED_STATION_MIN_DISTANCE = + ARRIVED_MAX_THRESHOLD + getAccuracyBonus(Number.MAX_VALUE); + +type Coordinates = { latitude: number; longitude: number }; + +const toCoordinates = (s: Station | undefined): Coordinates | null => + s?.latitude != null && s.longitude != null + ? { latitude: s.latitude, longitude: s.longitude } + : null; + +/** + * 現在駅より先にある通過駅のうち、現在地がすでに通り過ぎたものを返す。 + * + * 現在駅(stationState.station)は、最寄り駅の到着圏(半径200m前後)に測位が入ったときにしか + * 進まない。高速で通過する駅では到着圏を横切る時間が数秒しかなく、背景で測位がまとめて + * 届く間隔(約10秒)より短いため、通過駅を取りこぼして現在駅が後方に取り残される。 + * 取り残されると次駅・駅間の閾値・路線図上の現在位置が揃ってずれたままになる。 + * + * 現在地が載っている区間を、進行方向に並んだ駅間のうち「両端までの距離の和と駅間距離の + * 差」が最小のものとして選ぶ。その区間の始点が現在駅より先なら、始点を通過したとみなす。 + * + * 現在駅の先に続く通過駅の区間だけを候補にし、停車駅は飛び越えない。停車駅は停車中に + * 測位が届き続けるので到着判定で拾えるはずで、拾えていないなら測位の側を疑う方が安全。 + * 停車駅へ進めると到着通知やETAの基準駅まで動いてしまう。 + * + * @param orderedStations 進行方向順に並んだ駅(ループ線は対象外) + */ +export const findPassedStation = ( + orderedStations: Station[], + currentStation: Station | null | undefined, + latitude: number | null | undefined, + longitude: number | null | undefined, + accuracy: number | null | undefined +): Station | undefined => { + if (!currentStation || latitude == null || longitude == null) { + return undefined; + } + + const idMatchIndex = orderedStations.findIndex( + (s) => s.id === currentStation.id + ); + const currentIndex = + idMatchIndex !== -1 + ? idMatchIndex + : orderedStations.findIndex((s) => s.groupId === currentStation.groupId); + if (currentIndex === -1) { + return undefined; + } + + const current: Coordinates = { latitude, longitude }; + let bestIndex = -1; + let bestExcess = Number.POSITIVE_INFINITY; + + for (let i = currentIndex; i < orderedStations.length - 1; i++) { + const start = orderedStations[i]; + // 現在駅より先は、通過駅が続く間だけを候補にする + if (i > currentIndex && !getIsPass(start)) { + break; + } + const startCoordinates = toCoordinates(start); + const endCoordinates = toCoordinates(orderedStations[i + 1]); + if (!startCoordinates || !endCoordinates) { + continue; + } + const excess = + getDistance(current, startCoordinates) + + getDistance(current, endCoordinates) - + getDistance(startCoordinates, endCoordinates); + if (excess < bestExcess) { + bestExcess = excess; + bestIndex = i; + } + } + + if (bestIndex <= currentIndex) { + return undefined; + } + + const passed = orderedStations[bestIndex]; + const passedCoordinates = toCoordinates(passed); + const nextCoordinates = toCoordinates(orderedStations[bestIndex + 1]); + if (!passed || !passedCoordinates || !nextCoordinates) { + return undefined; + } + + // 測位の誤差ぶんは手前にいる可能性があるとみなし、誤差を差し引いても + // 通過駅の到着圏の外で、かつ通過駅より次の駅に近い場合だけ通過を確定する + const uncertainty = + accuracy != null && Number.isFinite(accuracy) && accuracy > 0 + ? accuracy + : 0; + const distanceFromPassed = getDistance(current, passedCoordinates); + const distanceToNext = getDistance(current, nextCoordinates); + const sectionDistance = getDistance(passedCoordinates, nextCoordinates); + if (distanceFromPassed - uncertainty < PASSED_STATION_MIN_DISTANCE) { + return undefined; + } + if (distanceToNext + uncertainty >= sectionDistance) { + return undefined; + } + + return passed; +}; From 75117bcbbfa1736792382ae316225395c0088b3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:05:17 +0900 Subject: [PATCH 02/17] Bump version for canary release (#7170) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a32f9258f2..90acf9b387 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000837 + versionCode 100000839 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000837 + versionCode 100000839 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000837 + versionCode 100000839 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index bfae189095..7223e819cb 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000838 + versionCode = 100000840 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000838 + versionCode = 100000840 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000838 + versionCode = 100000840 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 879e9be054..8da85dee78 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2948', + buildNumber: '2949', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000837, + versionCode: 100000839, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index aebdd0c0d8..29082d913a 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2948; + CURRENT_PROJECT_VERSION = 2949; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 27c8f5e2bcd36281110a4abd5975d21cf46e1fbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:16:54 +0900 Subject: [PATCH 03/17] Bump fast-uri from 3.1.7 to 3.1.8 (#7166) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f71731c5b..da89386d53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10633,9 +10633,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", From fdc18901219d22cbc222cdc1b00cf6300ce2cd5e Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 00:17:20 +0900 Subject: [PATCH 04/17] =?UTF-8?q?=E7=89=B9=E6=80=A5=E3=81=B2=E3=81=9F?= =?UTF-8?q?=E3=81=A1=E5=93=81=E5=B7=9D=E2=86=92=E4=BB=99=E5=8F=B0=E3=81=AE?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=83=85=E5=A0=B1=E3=82=B7=E3=83=9F=E3=83=A5?= =?UTF-8?q?=E3=83=AC=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E7=94=A8GPX?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=20(#7168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/gpx/HitachiShinagawaSendai.gpx | 55185 ++++++++++++++++++++++++ docs/location-simulation.md | 10 + 2 files changed, 55195 insertions(+) create mode 100644 assets/gpx/HitachiShinagawaSendai.gpx diff --git a/assets/gpx/HitachiShinagawaSendai.gpx b/assets/gpx/HitachiShinagawaSendai.gpx new file mode 100644 index 0000000000..10074be020 --- /dev/null +++ b/assets/gpx/HitachiShinagawaSendai.gpx @@ -0,0 +1,55185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/location-simulation.md b/docs/location-simulation.md index 66c8034cf7..f08d1fadd0 100644 --- a/docs/location-simulation.md +++ b/docs/location-simulation.md @@ -253,6 +253,7 @@ npm run gpx:generate -- \ | `assets/gpx/SampleJY.gpx` | 山手線。外部ツールで記録した実走行ログ | | `assets/gpx/SampleTohokuShinkansen.gpx` | 東北新幹線 盛岡→仙台。最高 320km/h、一ノ関に停車、通過駅 5 駅を経由 | | `assets/gpx/KeioSpecialExpress.gpx` | 京王線 特急 新宿→京王八王子。到着時間推定に合わせて走る | +| `assets/gpx/HitachiShinagawaSendai.gpx` | 特急ひたち 品川→仙台。到着時間推定に合わせて走る | | `assets/gpx/KatamachiRapid.gpx` | 片町線 快速 京田辺→木津。駅間 2.3km 前後の全駅停車 | | `assets/gpx/SobuRapid.gpx` | 総武快速線 錦糸町→津田沼。駅間 3.6〜7.6km (線路の長さ) の全駅停車 | | `assets/gpx/FLinerSeibu.gpx` | Fライナー相当 元町・中華街→飯能。地下鉄の電波環境付き | @@ -266,6 +267,15 @@ npm run gpx:generate -- \ 同じコマンドを再実行するとバイト単位で同じ内容が得られる。京王線の特急は到着時間 推定の較正テーブルに無いので、所要時間は一般則による推定値になる。 +`HitachiShinagawaSendai.gpx` は、通過駅が長く続く在来線特急のサンプル。東海道線・上野東京ライン・ +常磐快速線・常磐線の 4 路線を直通し、経路 94 駅のうち 30 駅に停車する。`--line-group 116` で +生成した。約 307 分・18394 点と同梱のサンプルで最も長い。巡航速度は到着時間推定に合わせて +決まり、最高でも 82km/h にとどまる。 + +停車駅は StationAPI の `stopCondition` に従う。列車によって停まる駅は一部停車 (`PartialStop`)・ +一部通過 (`Partial`) になっており、生成時はどちらも停車として扱う (柏・土浦など 9 駅)。 +そのため、実在するどの 1 本の停車パターンとも一致しない。 + `FLinerSeibu.gpx` は地下鉄の電波環境を持つ唯一のサンプル。いわゆる F ライナーだが、 StationAPI に「F ライナー」という種別は無く、みなとみらい線・東急東横線が特急、 副都心線が急行、西武有楽町線・西武池袋線が快速急行という組み合わせ (`lineGroupId` From 25bf85fef8be9f9ab5d941e0ad4ff56592bad2de Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 00:45:22 +0900 Subject: [PATCH 05/17] =?UTF-8?q?=E6=8C=AF=E3=82=8A=E8=BF=94=E3=82=8A?= =?UTF-8?q?=E3=81=AE=E3=82=88=E3=81=8F=E4=B9=97=E3=81=A3=E3=81=9F=E8=B7=AF?= =?UTF-8?q?=E7=B7=9A=E3=82=92=E4=B9=97=E3=81=A3=E3=81=9F=E5=9B=9E=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E5=A4=9A=E3=81=84=E9=A0=86=E3=81=AB=E4=B8=A6=E3=81=B9?= =?UTF-8?q?=E3=80=81=E3=83=90=E3=83=BC=E3=82=82=E5=9B=9E=E6=95=B0=E3=81=A7?= =?UTF-8?q?=E6=8F=8F=E3=81=8F=20(#7172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/translations/en.json | 4 ++-- assets/translations/ja.json | 4 ++-- src/screens/RideReview.tsx | 4 ++-- src/utils/rideStats.test.ts | 27 ++++++++++++++++----------- src/utils/rideStats.ts | 4 ++-- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 0a04781b6b..e3ac4939bd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -399,7 +399,7 @@ "rideReviewPrefecturesShowAll": "Show all (%{count} more)", "rideReviewPrefecturesUnrecorded": "Rides whose prefectures are unknown (%{count}) are not included. These include rides recorded before prefectures were saved.", "rideReviewPrefecturesNone": "The prefectures of the rides in this period are unknown. These include rides recorded before prefectures were saved.", - "rideReviewLineValue": "%{distance} km · %{count} rides", + "rideReviewLineValue": "%{count} rides · %{distance} km", "rideReviewNote": "Distances are the sum of track lengths between the stations where an arrival was detected. Rides in auto mode are not included.", "rideReviewEmptyTitle": "No rides yet", "rideReviewEmptyBody": "Choose a line and ride, and the distance and time are recorded at each station you arrive at. Rides taken before you turned this on are not included.", @@ -420,7 +420,7 @@ "rideReviewWalkthroughRouteMapTitle": "Routes", "rideReviewWalkthroughRouteMapDescription": "The sections you rode are drawn on the map in each line's color. Open the map in full screen with the button at the top right to move it around.", "rideReviewWalkthroughTopLinesTitle": "Most ridden lines", - "rideReviewWalkthroughTopLinesDescription": "Lines are listed in order of the distance you rode on them in the period.", + "rideReviewWalkthroughTopLinesDescription": "Lines are listed by how many times you rode them in the period. Lines with the same count are ordered by distance.", "rideReviewTabIntroOpen": "Open ride review", "rideReviewRouteMap": "Routes", "rideReviewRouteMapTitle": "Routes: %{range}", diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 6c7c0b44c6..dc3a3380e3 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -400,7 +400,7 @@ "rideReviewPrefecturesShowAll": "すべて表示(ほか %{count} 都道府県)", "rideReviewPrefecturesUnrecorded": "都道府県が分からない乗車(%{count}回)は、この集計に入れていません。都道府県を記録する前の乗車も、これに含まれます。", "rideReviewPrefecturesNone": "この期間の乗車は、どれも都道府県が分かりません。都道府県を記録する前の乗車も、これに含まれます。", - "rideReviewLineValue": "%{distance} km・%{count}回", + "rideReviewLineValue": "%{count}回・%{distance} km", "rideReviewNote": "到着を検出した駅のあいだを、線路の長さで合計しています。オートモードの乗車は含みません。", "rideReviewEmptyTitle": "まだ記録がありません", "rideReviewEmptyBody": "路線を選んで乗車すると、到着した駅ごとに距離と時間を記録します。振り返りを有効にする前の乗車は集計できません。", @@ -421,7 +421,7 @@ "rideReviewWalkthroughRouteMapTitle": "移動経路", "rideReviewWalkthroughRouteMapDescription": "乗った区間を、路線の色で地図に描きます。右上のボタンで全画面にすると、地図を動かせます。", "rideReviewWalkthroughTopLinesTitle": "よく乗った路線", - "rideReviewWalkthroughTopLinesDescription": "期間内に長い距離を乗った路線の順に並べます。", + "rideReviewWalkthroughTopLinesDescription": "期間内に乗った回数の多い路線の順に並べます。回数が同じときは、長い距離を乗った路線を先にします。", "rideReviewTabIntroOpen": "振り返りを見る", "rideReviewRouteMap": "移動経路", "rideReviewRouteMapTitle": "%{range}の移動経路", diff --git a/src/screens/RideReview.tsx b/src/screens/RideReview.tsx index 3cd2350513..61cadd8460 100644 --- a/src/screens/RideReview.tsx +++ b/src/screens/RideReview.tsx @@ -496,7 +496,7 @@ const RouteMapCard = ({ const TopLines = ({ stats }: { stats: RideStats }) => { const colors = useAppColors(); const lines = stats.lines.slice(0, TOP_LINES_LIMIT); - const max = lines[0]?.distanceMeters ?? 0; + const max = lines[0]?.rideCount ?? 0; if (lines.length === 0) { return null; } @@ -530,7 +530,7 @@ const TopLines = ({ stats }: { stats: RideStats }) => { style={[ styles.lineFill, { - width: `${max > 0 ? (line.distanceMeters / max) * 100 : 0}%`, + width: `${max > 0 ? (line.rideCount / max) * 100 : 0}%`, backgroundColor: color, }, ]} diff --git a/src/utils/rideStats.test.ts b/src/utils/rideStats.test.ts index c3dbb31ac9..8d6aff7a2e 100644 --- a/src/utils/rideStats.test.ts +++ b/src/utils/rideStats.test.ts @@ -200,7 +200,7 @@ describe('summarizeRides', () => { expect(year.buckets[1].distanceMeters).toBe(1000); }); - it('路線ごとの距離は到着した側の駅の路線に数え、長い順に並べる', () => { + it('路線ごとの距離は到着した側の駅の路線に数え、同じ回数なら長い順に並べる', () => { const start = at('2026-09-28T08:00:00Z'); // 中央線快速から総武線へ直通する乗車(中央線 1000m・総武線 2000m) const through = ride('through', start, [ @@ -217,20 +217,25 @@ describe('summarizeRides', () => { ]); }); - it('同じ距離の路線は乗った回数の多い順に並べる', () => { - const start = at('2026-09-28T08:00:00Z'); - const through = ride('through', start, [ - {}, - { lineId: 22, lineName: '総武線', lineColor: '#FFD400' }, - { lineId: 22, lineName: '総武線', lineColor: '#FFD400' }, - ]); - const chuo = ride('chuo', at('2026-09-29T08:00:00Z'), [{}]); - const stats = summarizeRides([through, chuo], 'week', week); + it('距離が短くても乗った回数の多い路線を先に並べる', () => { + // 総武線を1回で5000m、中央線快速を2回で計2000m + const sobu = ride( + 'sobu', + at('2026-09-28T08:00:00Z'), + Array.from({ length: 5 }, () => ({ + lineId: 22, + lineName: '総武線', + lineColor: '#FFD400', + })) + ); + const chuo1 = ride('chuo1', at('2026-09-29T08:00:00Z'), [{}]); + const chuo2 = ride('chuo2', at('2026-09-30T08:00:00Z'), [{}]); + const stats = summarizeRides([sobu, chuo1, chuo2], 'week', week); expect( stats.lines.map((l) => [l.lineId, l.distanceMeters, l.rideCount]) ).toEqual([ [11, 2000, 2], - [22, 2000, 1], + [22, 5000, 1], ]); }); }); diff --git a/src/utils/rideStats.ts b/src/utils/rideStats.ts index e0d521b875..6eafa62b84 100644 --- a/src/utils/rideStats.ts +++ b/src/utils/rideStats.ts @@ -128,7 +128,7 @@ export type RideStats = { rideCount: number; // グラフ用。週は曜日ごとの7つ、月は日ごとの日数ぶん、年は月ごとの12個 buckets: RideStatsBucket[]; - // 距離の長い順。同じ距離なら乗った回数の多い順 + // 乗った回数の多い順。同じ回数なら距離の長い順 lines: RideLineStats[]; }; @@ -220,7 +220,7 @@ export const summarizeRides = ( rideCount, buckets, lines: [...lines.values()].sort( - (a, b) => b.distanceMeters - a.distanceMeters || b.rideCount - a.rideCount + (a, b) => b.rideCount - a.rideCount || b.distanceMeters - a.distanceMeters ), }; }; From 6a5f35225a46ade74b7b98f2e5e5a8eca04810c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:46:17 +0900 Subject: [PATCH 06/17] Bump @fastify/busboy from 3.2.0 to 3.2.2 (#7171) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index da89386d53..a1bf3fd307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2945,9 +2945,9 @@ } }, "node_modules/@fastify/busboy": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", - "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.2.tgz", + "integrity": "sha512-yXSS27qPExaXeuLvMRMXOLtpipzfQYNjG3FkunDWKGfMYjKuhFXko9CVzqxm8jcF+lmtS9Fd89QNdh9XDjnbNg==", "dev": true, "license": "MIT" }, From d89dd28fa728dda8fa11ebfcffdc8e6d8cbf7466 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 01:22:27 +0900 Subject: [PATCH 07/17] Bump version for canary release (#7174) Co-authored-by: TinyKitten <32848922+TinyKitten@users.noreply.github.com> --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 90acf9b387..c02c4aaf4c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000839 + versionCode 100000841 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000839 + versionCode 100000841 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000839 + versionCode 100000841 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index 7223e819cb..dd2fe4bfe1 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000840 + versionCode = 100000842 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000840 + versionCode = 100000842 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000840 + versionCode = 100000842 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 8da85dee78..361aef3040 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2949', + buildNumber: '2950', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000839, + versionCode: 100000841, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 29082d913a..3877a290e7 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2949; + CURRENT_PROJECT_VERSION = 2950; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From eb4de30ba912d14cd160cd97562a02f7c1a60d35 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 12:42:43 +0900 Subject: [PATCH 08/17] =?UTF-8?q?=E5=81=9C=E8=BB=8A=E9=A7=85=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=82=92=E9=96=8B?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB=E7=8F=BE=E5=9C=A8?= =?UTF-8?q?=E3=81=AE=E6=9C=80=E5=AF=84=E3=82=8A=E9=A7=85=E3=81=BE=E3=81=A7?= =?UTF-8?q?=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F=20(#7175)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RouteInfoModal.test.tsx | 156 ++++++++++++++++++ src/components/RouteInfoModal.tsx | 48 +++++- .../SelectBoundModal.render.test.tsx | 85 ++++++++++ src/components/SelectBoundModal.tsx | 21 +++ 4 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 src/components/RouteInfoModal.test.tsx diff --git a/src/components/RouteInfoModal.test.tsx b/src/components/RouteInfoModal.test.tsx new file mode 100644 index 0000000000..ef9dc674d7 --- /dev/null +++ b/src/components/RouteInfoModal.test.tsx @@ -0,0 +1,156 @@ +import { act, render } from '@testing-library/react-native'; +import { useAtomValue } from 'jotai'; +import type React from 'react'; +import type { Station } from '~/@types/graphql'; +import { LIGHT_APP_COLORS } from '~/constants/colorScheme'; +import { appColorsAtom } from '~/store/atoms/colorScheme'; +import { isLEDThemeAtom } from '~/store/atoms/theme'; +import { RouteInfoModal } from './RouteInfoModal'; + +jest.mock('jotai', () => ({ + ...jest.requireActual('jotai'), + useAtomValue: jest.fn(), +})); + +// 一覧の初回レイアウト(onLoad)とスクロールの呼び出しだけを確かめたいので、 +// FlashList は ref に scrollToIndex を生やし onLoad を受け取るだけの入れ物にする +const mockScrollToIndex = jest.fn(); +const mockFlashListOnLoad: { current: (() => void) | null } = { + current: null, +}; +jest.mock('@shopify/flash-list', () => { + const ReactModule = require('react'); + return { + FlashList: ReactModule.forwardRef( + (props: { onLoad?: () => void }, ref: React.Ref) => { + ReactModule.useImperativeHandle(ref, () => ({ + scrollToIndex: mockScrollToIndex, + })); + mockFlashListOnLoad.current = props.onLoad ?? null; + return null; + } + ), + }; +}); + +// 実物は閉じると中身をアンマウントするので、それに合わせる +jest.mock('./CustomModal', () => ({ + CustomModal: (props: { visible: boolean; children?: React.ReactNode }) => + props.visible ? (props.children ?? null) : null, +})); + +jest.mock('~/translation', () => ({ + isJapanese: true, + translate: jest.fn((key: string) => key), +})); + +const stations = [1, 2, 3, 4, 5].map( + (id) => + ({ + id, + groupId: id, + name: `駅${id}`, + nameRoman: `Station ${id}`, + line: { id: 10 }, + lines: [{ id: 10 }], + }) as unknown as Station +); + +const renderModal = (props: { + visible: boolean; + currentStation: Station | null; + stations?: Station[]; +}) => ( + +); + +describe('RouteInfoModal', () => { + beforeEach(() => { + (useAtomValue as jest.Mock).mockImplementation((atom: unknown) => { + if (atom === appColorsAtom) return LIGHT_APP_COLORS; + if (atom === isLEDThemeAtom) return false; + return null; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + mockFlashListOnLoad.current = null; + }); + + it('一覧の初回レイアウト後に現在の最寄り駅までヘッダーの高さだけずらしてスクロールする', () => { + render(renderModal({ visible: true, currentStation: stations[3] })); + expect(mockScrollToIndex).not.toHaveBeenCalled(); + + act(() => mockFlashListOnLoad.current?.()); + + expect(mockScrollToIndex).toHaveBeenCalledTimes(1); + const [params] = mockScrollToIndex.mock.calls[0]; + expect(params.index).toBe(3); + expect(params.animated).toBe(false); + expect(params.viewOffset).toBeLessThan(0); + }); + + it('最寄り駅が先頭の駅ならスクロールしない', () => { + render(renderModal({ visible: true, currentStation: stations[0] })); + act(() => mockFlashListOnLoad.current?.()); + + expect(mockScrollToIndex).not.toHaveBeenCalled(); + }); + + it('最寄り駅が一覧に無ければスクロールしない', () => { + render( + renderModal({ + visible: true, + currentStation: { id: 99, groupId: 99 } as Station, + }) + ); + act(() => mockFlashListOnLoad.current?.()); + + expect(mockScrollToIndex).not.toHaveBeenCalled(); + }); + + it('乗換駅の行が間引かれていても groupId で同じ駅の行へスクロールする', () => { + // 3 と 30 は同じ乗換駅。手前の区間の 3 は一覧から間引かれる + const transferStations = [ + stations[0], + stations[1], + stations[2], + { ...stations[2], id: 30 } as Station, + stations[3], + ]; + render( + renderModal({ + visible: true, + currentStation: stations[2], + stations: transferStations, + }) + ); + act(() => mockFlashListOnLoad.current?.()); + + expect(mockScrollToIndex.mock.calls[0][0].index).toBe(2); + }); + + it('開き直すたびにもう一度スクロールする', () => { + const { rerender } = render( + renderModal({ visible: true, currentStation: stations[3] }) + ); + act(() => mockFlashListOnLoad.current?.()); + expect(mockScrollToIndex).toHaveBeenCalledTimes(1); + + rerender(renderModal({ visible: false, currentStation: stations[3] })); + rerender(renderModal({ visible: true, currentStation: stations[3] })); + // 開き直した直後は前回の onLoad を引き継がず、新しい一覧のレイアウトを待つ + expect(mockScrollToIndex).toHaveBeenCalledTimes(1); + + act(() => mockFlashListOnLoad.current?.()); + expect(mockScrollToIndex).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/components/RouteInfoModal.tsx b/src/components/RouteInfoModal.tsx index 2d89d07c54..e137825dff 100644 --- a/src/components/RouteInfoModal.tsx +++ b/src/components/RouteInfoModal.tsx @@ -1,7 +1,7 @@ -import { FlashList } from '@shopify/flash-list'; +import { FlashList, type FlashListRef } from '@shopify/flash-list'; import { BlurView } from 'expo-blur'; import { useAtomValue } from 'jotai'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Platform, StyleSheet, useWindowDimensions, View } from 'react-native'; import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; import type { Station, TrainType } from '~/@types/graphql'; @@ -100,6 +100,8 @@ type Props = { wantedDestinationGroupId?: number | null; /** 終着駅の設定トグル */ onToggleDestination?: (station: Station) => void; + /** 開いたときにスクロールして見せる駅(現在の最寄り駅) */ + currentStation?: Station | null; }; export const RouteInfoModal = ({ @@ -113,6 +115,7 @@ export const RouteInfoModal = ({ onToggleNotification, wantedDestinationGroupId, onToggleDestination, + currentStation, }: Props) => { const isLEDTheme = useAtomValue(isLEDThemeAtom); const colors = useAtomValue(appColorsAtom); @@ -253,6 +256,45 @@ export const RouteInfoModal = ({ [stations] ); + const currentStationIndex = useMemo(() => { + if (!currentStation) return -1; + // 乗換駅は前後どちらかの区間の行が間引かれているので、ID で見つからなければ groupId で探す + const byId = deduppedStations.findIndex((s) => s.id === currentStation.id); + if (byId !== -1) return byId; + return deduppedStations.findIndex( + (s) => s.groupId === currentStation.groupId + ); + }, [currentStation, deduppedStations]); + + const listRef = useRef>(null); + // CustomModal は閉じると中身をアンマウントするので、開くたびに一覧の初回レイアウトを待ってからスクロールする + const [isListLoaded, setIsListLoaded] = useState(false); + const hasScrolledToCurrentRef = useRef(false); + const headerHeightRef = useRef(headerHeight); + headerHeightRef.current = headerHeight; + + useEffect(() => { + if (!visible) { + setIsListLoaded(false); + hasScrolledToCurrentRef.current = false; + } + }, [visible]); + + useEffect(() => { + if (!visible || !isListLoaded || hasScrolledToCurrentRef.current) return; + if (currentStationIndex <= 0) return; + hasScrolledToCurrentRef.current = true; + // 一覧の上端はヘッダーの裏に潜るので、ヘッダーの高さだけずらしてヘッダーの直下に出す。 + // 末尾付近の駅は FlashList が最大スクロール量で止める + listRef.current?.scrollToIndex({ + index: currentStationIndex, + animated: false, + viewOffset: -headerHeightRef.current, + }); + }, [visible, isListLoaded, currentStationIndex]); + + const handleListLoad = useCallback(() => setIsListLoaded(true), []); + const dynamicMinHeight = useMemo(() => { const count = deduppedStations.length; const content = headerHeight + count * 80 + Math.max(0, count - 1) * 8 + 72; @@ -315,7 +357,9 @@ export const RouteInfoModal = ({ + ref={listRef} data={deduppedStations} + onLoad={handleListLoad} renderItem={renderItem} keyExtractor={keyExtractor} ItemSeparatorComponent={EmptyLineSeparator} diff --git a/src/components/SelectBoundModal.render.test.tsx b/src/components/SelectBoundModal.render.test.tsx index 3f61a7292a..962e95bdf9 100644 --- a/src/components/SelectBoundModal.render.test.tsx +++ b/src/components/SelectBoundModal.render.test.tsx @@ -542,6 +542,91 @@ describe('SelectBoundModal', () => { expect(props.stations.map((station) => station.groupId)).toEqual([1, 2, 3]); }); + describe('停車駅一覧を開いたときに見せる駅', () => { + const getLastCurrentStation = () => { + const lastCall = + mockRouteInfoModal.mock.calls[mockRouteInfoModal.mock.calls.length - 1]; + return (lastCall?.[0] as { currentStation: { id: number } | null }) + .currentStation; + }; + const renderModal = () => + render( + + ); + const routeStations = [1, 2, 3, 4, 5].map((id) => ({ + id, + groupId: id, + line: { id: 10 }, + lines: [{ id: 10 }], + })); + const getIsPassMock = jest.requireMock('~/utils/isPass') + .default as jest.Mock; + + afterEach(() => { + getIsPassMock.mockImplementation(() => false); + }); + + it('最寄り駅に停車するならその駅を渡す', () => { + mockAtomValues({ + pendingStation: { id: 3, groupId: 3, lines: [{ id: 10 }] }, + pendingStations: routeStations, + pendingLine: { id: 10, name: '山手線', nameRoman: 'Yamanote Line' }, + }); + + renderModal(); + + expect(getLastCurrentStation()?.id).toBe(3); + }); + + it('最寄り駅を通過するなら経路上で最も近い停車駅を渡す', () => { + // 2・3・5 を通過。3 からは 4 が最も近い + getIsPassMock.mockImplementation((s?: { id: number }) => + [2, 3, 5].includes(s?.id ?? -1) + ); + mockAtomValues({ + pendingStation: { id: 3, groupId: 3, lines: [{ id: 10 }] }, + pendingStations: routeStations, + pendingLine: { id: 10, name: '山手線', nameRoman: 'Yamanote Line' }, + }); + + renderModal(); + + expect(getLastCurrentStation()?.id).toBe(4); + }); + + it('前後の停車駅が等距離なら手前の駅を渡す', () => { + getIsPassMock.mockImplementation((s?: { id: number }) => s?.id === 3); + mockAtomValues({ + pendingStation: { id: 3, groupId: 3, lines: [{ id: 10 }] }, + pendingStations: routeStations, + pendingLine: { id: 10, name: '山手線', nameRoman: 'Yamanote Line' }, + }); + + renderModal(); + + expect(getLastCurrentStation()?.id).toBe(2); + }); + + it('最寄り駅が経路外なら何も渡さない', () => { + mockAtomValues({ + pendingStation: { id: 99, groupId: 99, lines: [{ id: 10 }] }, + pendingStations: routeStations, + pendingLine: { id: 10, name: '山手線', nameRoman: 'Yamanote Line' }, + }); + + renderModal(); + + expect(getLastCurrentStation()).toBeNull(); + }); + }); + // 乗車駅が未設定だと effectiveStation は stations[0] へ倒れるため、 // そこから向きを決めると常に INBOUND になってしまう it('乗車駅が未設定の場合はGPS確定駅の座標から保存する向きを決める', async () => { diff --git a/src/components/SelectBoundModal.tsx b/src/components/SelectBoundModal.tsx index 5a382041cb..cbe8626148 100644 --- a/src/components/SelectBoundModal.tsx +++ b/src/components/SelectBoundModal.tsx @@ -910,6 +910,26 @@ export const SelectBoundModal: React.FC = ({ [stations] ); + // 停車駅一覧を開いたときに見せる駅。最寄り駅を通過する種別では一覧に出ないので、 + // 経路上で最も近い停車駅で代える(等距離なら手前の駅) + const routeInfoCurrentStation = useMemo(() => { + if (!station) return null; + const byId = stations.findIndex((s) => s.id === station.id); + const index = + byId !== -1 + ? byId + : stations.findIndex((s) => s.groupId === station.groupId); + if (index === -1) return null; + if (!getIsPass(stations[index])) return stations[index]; + for (let distance = 1; distance < stations.length; distance++) { + const before = stations[index - distance]; + if (before && !getIsPass(before)) return before; + const after = stations[index + distance]; + if (after && !getIsPass(after)) return after; + } + return null; + }, [station, stations]); + const isBus = isBusLine(line); const trainTypeModalLine = useMemo(() => { @@ -1106,6 +1126,7 @@ export const SelectBoundModal: React.FC = ({ onToggleNotification={handleToggleNotification} wantedDestinationGroupId={wantedDestination?.groupId ?? null} onToggleDestination={handleToggleDestination} + currentStation={routeInfoCurrentStation} /> Date: Mon, 5 Oct 2026 12:44:36 +0900 Subject: [PATCH 09/17] Bump version for canary release (#7177) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c02c4aaf4c..f9a8a7b9e3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000841 + versionCode 100000843 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000841 + versionCode 100000843 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000841 + versionCode 100000843 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index dd2fe4bfe1..b26a784dc5 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000842 + versionCode = 100000844 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000842 + versionCode = 100000844 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000842 + versionCode = 100000844 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 361aef3040..6cfc8e236c 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2950', + buildNumber: '2951', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000841, + versionCode: 100000843, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 3877a290e7..1644438b85 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2950; + CURRENT_PROJECT_VERSION = 2951; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From c50a8d36c0f135cf21cb1d1144fcb26889f0f021 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 13:24:22 +0900 Subject: [PATCH 10/17] =?UTF-8?q?=E5=81=9C=E8=BB=8A=E9=A7=85=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=81=A7=E6=9C=80?= =?UTF-8?q?=E5=AF=84=E3=82=8A=E9=A7=85=E3=81=BE=E3=81=A7=E3=81=AE=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB=E3=82=92=E3=82=A2=E3=83=8B?= =?UTF-8?q?=E3=83=A1=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=95=E3=81=9B?= =?UTF-8?q?=E3=82=8B=20(#7178)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RouteInfoModal.test.tsx | 4 ++-- src/components/RouteInfoModal.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/RouteInfoModal.test.tsx b/src/components/RouteInfoModal.test.tsx index ef9dc674d7..ec807bb383 100644 --- a/src/components/RouteInfoModal.test.tsx +++ b/src/components/RouteInfoModal.test.tsx @@ -85,7 +85,7 @@ describe('RouteInfoModal', () => { mockFlashListOnLoad.current = null; }); - it('一覧の初回レイアウト後に現在の最寄り駅までヘッダーの高さだけずらしてスクロールする', () => { + it('一覧の初回レイアウト後に現在の最寄り駅までヘッダーの高さだけずらしてアニメーションでスクロールする', () => { render(renderModal({ visible: true, currentStation: stations[3] })); expect(mockScrollToIndex).not.toHaveBeenCalled(); @@ -94,7 +94,7 @@ describe('RouteInfoModal', () => { expect(mockScrollToIndex).toHaveBeenCalledTimes(1); const [params] = mockScrollToIndex.mock.calls[0]; expect(params.index).toBe(3); - expect(params.animated).toBe(false); + expect(params.animated).toBe(true); expect(params.viewOffset).toBeLessThan(0); }); diff --git a/src/components/RouteInfoModal.tsx b/src/components/RouteInfoModal.tsx index e137825dff..638f98d09a 100644 --- a/src/components/RouteInfoModal.tsx +++ b/src/components/RouteInfoModal.tsx @@ -285,10 +285,12 @@ export const RouteInfoModal = ({ if (currentStationIndex <= 0) return; hasScrolledToCurrentRef.current = true; // 一覧の上端はヘッダーの裏に潜るので、ヘッダーの高さだけずらしてヘッダーの直下に出す。 - // 末尾付近の駅は FlashList が最大スクロール量で止める + // 末尾付近の駅は FlashList が最大スクロール量で止める。 + // どこまで進んだかが目で追えるようアニメーションさせる。遠い駅では FlashList が + // 手前(画面2枚分)まで一気に飛んでから残りをアニメーションする listRef.current?.scrollToIndex({ index: currentStationIndex, - animated: false, + animated: true, viewOffset: -headerHeightRef.current, }); }, [visible, isListLoaded, currentStationIndex]); From e2cdec5c0128ea2ae3a1ee7d035e942c3697b306 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:25:51 +0900 Subject: [PATCH 11/17] Bump version for canary release (#7180) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f9a8a7b9e3..0d2bf4077f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000843 + versionCode 100000845 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000843 + versionCode 100000845 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000843 + versionCode 100000845 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index b26a784dc5..5cbfa113cc 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000844 + versionCode = 100000846 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000844 + versionCode = 100000846 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000844 + versionCode = 100000846 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 6cfc8e236c..048a19c526 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2951', + buildNumber: '2952', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000843, + versionCode: 100000845, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 1644438b85..7c0ca128c5 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2951; + CURRENT_PROJECT_VERSION = 2952; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 949432dde86e4ce744d6cef5fc55a23640681f0f Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 13:41:04 +0900 Subject: [PATCH 12/17] =?UTF-8?q?=E5=81=9C=E8=BB=8A=E9=A7=85=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=81=AE=E6=9C=80=E5=AF=84=E3=82=8A=E9=A7=85=E3=81=AE?= =?UTF-8?q?=E6=A4=9C=E7=B4=A2=E3=81=A7=20id=E3=83=BBgroupId=20=E3=81=8C=20?= =?UTF-8?q?null=20=E3=81=AE=E9=A7=85=E3=81=A9=E3=81=86=E3=81=97=E3=82=92?= =?UTF-8?q?=E5=90=8C=E3=81=98=E9=A7=85=E3=81=A8=E3=81=BF=E3=81=AA=E3=81=95?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E4=BF=AE=E6=AD=A3=20(#7181?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RouteInfoModal.test.tsx | 18 ++++++++++++++++++ src/components/RouteInfoModal.tsx | 8 ++++++-- .../SelectBoundModal.render.test.tsx | 15 +++++++++++++++ src/components/SelectBoundModal.tsx | 6 ++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/components/RouteInfoModal.test.tsx b/src/components/RouteInfoModal.test.tsx index ec807bb383..85772fac5d 100644 --- a/src/components/RouteInfoModal.test.tsx +++ b/src/components/RouteInfoModal.test.tsx @@ -117,6 +117,24 @@ describe('RouteInfoModal', () => { expect(mockScrollToIndex).not.toHaveBeenCalled(); }); + it('最寄り駅の id・groupId が null でも、同じく null の別の駅の行へスクロールしない', () => { + const nullIdStation = { + ...stations[0], + id: null, + groupId: null, + } as unknown as Station; + render( + renderModal({ + visible: true, + currentStation: { id: null, groupId: null } as unknown as Station, + stations: [...stations, nullIdStation], + }) + ); + act(() => mockFlashListOnLoad.current?.()); + + expect(mockScrollToIndex).not.toHaveBeenCalled(); + }); + it('乗換駅の行が間引かれていても groupId で同じ駅の行へスクロールする', () => { // 3 と 30 は同じ乗換駅。手前の区間の 3 は一覧から間引かれる const transferStations = [ diff --git a/src/components/RouteInfoModal.tsx b/src/components/RouteInfoModal.tsx index 638f98d09a..7a32dd0eea 100644 --- a/src/components/RouteInfoModal.tsx +++ b/src/components/RouteInfoModal.tsx @@ -259,8 +259,12 @@ export const RouteInfoModal = ({ const currentStationIndex = useMemo(() => { if (!currentStation) return -1; // 乗換駅は前後どちらかの区間の行が間引かれているので、ID で見つからなければ groupId で探す - const byId = deduppedStations.findIndex((s) => s.id === currentStation.id); - if (byId !== -1) return byId; + // id・groupId は null になりうる。null どうしを同じ駅とみなさない + const byId = + currentStation.id != null + ? deduppedStations.findIndex((s) => s.id === currentStation.id) + : -1; + if (byId !== -1 || currentStation.groupId == null) return byId; return deduppedStations.findIndex( (s) => s.groupId === currentStation.groupId ); diff --git a/src/components/SelectBoundModal.render.test.tsx b/src/components/SelectBoundModal.render.test.tsx index 962e95bdf9..4f500d3f81 100644 --- a/src/components/SelectBoundModal.render.test.tsx +++ b/src/components/SelectBoundModal.render.test.tsx @@ -625,6 +625,21 @@ describe('SelectBoundModal', () => { expect(getLastCurrentStation()).toBeNull(); }); + + it('最寄り駅の id・groupId が null でも、同じく null の別の駅を最寄り駅とみなさない', () => { + mockAtomValues({ + pendingStation: { id: null, groupId: null, lines: [{ id: 10 }] }, + pendingStations: [ + ...routeStations, + { id: null, groupId: null, line: { id: 10 }, lines: [{ id: 10 }] }, + ], + pendingLine: { id: 10, name: '山手線', nameRoman: 'Yamanote Line' }, + }); + + renderModal(); + + expect(getLastCurrentStation()).toBeNull(); + }); }); // 乗車駅が未設定だと effectiveStation は stations[0] へ倒れるため、 diff --git a/src/components/SelectBoundModal.tsx b/src/components/SelectBoundModal.tsx index cbe8626148..a319e872da 100644 --- a/src/components/SelectBoundModal.tsx +++ b/src/components/SelectBoundModal.tsx @@ -914,9 +914,11 @@ export const SelectBoundModal: React.FC = ({ // 経路上で最も近い停車駅で代える(等距離なら手前の駅) const routeInfoCurrentStation = useMemo(() => { if (!station) return null; - const byId = stations.findIndex((s) => s.id === station.id); + // id・groupId は null になりうる。null どうしを同じ駅とみなさない + const byId = + station.id != null ? stations.findIndex((s) => s.id === station.id) : -1; const index = - byId !== -1 + byId !== -1 || station.groupId == null ? byId : stations.findIndex((s) => s.groupId === station.groupId); if (index === -1) return null; From 270acbcd4f9a50b406e57353efe83cc35451bb01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:42:51 +0900 Subject: [PATCH 13/17] Bump version for canary release (#7183) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 0d2bf4077f..2336c9da28 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000845 + versionCode 100000847 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000845 + versionCode 100000847 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000845 + versionCode 100000847 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index 5cbfa113cc..acd4fbdb73 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000846 + versionCode = 100000848 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000846 + versionCode = 100000848 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000846 + versionCode = 100000848 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 048a19c526..4dadb2fd48 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2952', + buildNumber: '2953', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000845, + versionCode: 100000847, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 7c0ca128c5..fe1ae24d7e 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2952; + CURRENT_PROJECT_VERSION = 2953; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 5472fb489b4adea7af5226ca50f6d7db4b2d16dd Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 18:42:45 +0900 Subject: [PATCH 14/17] =?UTF-8?q?Android=E3=81=A7=E6=9C=80=E8=BF=91?= =?UTF-8?q?=E4=BD=BF=E3=81=A3=E3=81=9F=E3=82=A2=E3=83=97=E3=83=AA=E3=81=8B?= =?UTF-8?q?=E3=82=89=E6=B6=88=E3=81=97=E3=81=A6=E9=96=8B=E3=81=8D=E7=9B=B4?= =?UTF-8?q?=E3=81=99=E3=81=A8=E7=AB=AF=E6=9C=AB=E5=86=85=E8=94=B5TTS?= =?UTF-8?q?=E3=81=8C=E9=B3=B4=E3=82=89=E3=81=AA=E3=81=84=E4=B8=8D=E5=85=B7?= =?UTF-8?q?=E5=90=88=E3=82=92=E4=BF=AE=E6=AD=A3=20(#7184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 33 +++ docs/spec/tts/remote-tts.md | 16 +- package.json | 7 + patches/expo-speech+57.0.1.patch | 354 +++++++++++++++++++------------ src/utils/nativeTtsVoice.ts | 12 +- 5 files changed, 274 insertions(+), 148 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 5f98f3b46b..3a46c5c9c3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,39 @@ CLAUDE.md「Security & Configuration Guardrails」に従い、依存更新後に 新しいエントリを上に追加する。 +## 2026-10-05 — Android で一覧から消して開き直すと端末内蔵 TTS が鳴らない不具合を修正 + +対象バージョン: v10.18.0 の本番で発生 + +### 内容 + +- 走行中にアプリを Android の「最近使ったアプリ」の一覧から消すと、MainActivity が破棄され、 + `expo-speech` の `OnActivityDestroys` が `TextToSpeech.shutdown()` を呼ぶ。プロセスはキャッシュとして残る。 +- 残ったプロセスのまま開き直すと、`expo-speech` は `by lazy` で作った shutdown 済みのインスタンスを使い続ける。 + `getVoices` と `speak` は `not bound to TTS engine` で失敗し続け、プロセスが終わるまで鳴らない。 +- Android の本番は Remote Config の `remote_tts_enabled_android=false` で端末内蔵 TTS を使うため、この経路に当たった。 +- `patches/expo-speech+57.0.1.patch` で、破棄したら参照を捨て、次に前面へ戻ってから作り直すようにした。 + 破棄から前面へ戻るまでは `speak` を受け付けず、一覧から消したアプリが読み上げないようにしている。 + TTS の状態は AsyncFunction と同じ `modulesQueue` のスレッドだけで扱い、ロックを使わない。 +- `expo-speech` は Android ではビルド済みの AAR(`local-maven-repo`)から取り込まれる。 + パッチをビルドに入れるため、`package.json` の `expo.autolinking.android.buildFromSource` に `expo-speech` を加えた。 +- 以前の `patches/expo-speech+57.0.1.patch`(#6457・#6804)も同じ理由でビルドに入っておらず、本番のバイナリに + 含まれていなかった。今回のパッチには含めていない。 + +### 確認 + +- `prodRelease`(署名だけ debug 鍵に差し替え)を Android 13 の実機に入れ、走行中に一覧から消して開き直す操作を 2 回行った。 + 消したあとの合成要求は 0 件で、開き直したあとは同じプロセスのまま日本語・英語とも合成された。 +- `buildFromSource` を外して作った同じ版では、同じ手順で `speak failed: not bound to TTS engine` になった。 +- `npm run lint`・`npm test`・`npm run typecheck` は成功した。 + +### 学び + +- patch-package で `node_modules` のネイティブのソースを書き換えても、Expo のモジュールがビルド済みの AAR を + 同梱していればビルドに入らない。ネイティブのパッチは `buildFromSource` に加え、APK の中にパッチの + コードがあることまで確かめる。 +- patch-package はパッチを作るときに `android/build` などの生成物も差分に含める。作る前に生成物を消す。 + ## 2026-09-23 — Android リリースビルドの Gradle デーモンのヒープ上限を 4096m に引き上げ 対象バージョン: v10.16.0 の本番ビルド失敗への対応 diff --git a/docs/spec/tts/remote-tts.md b/docs/spec/tts/remote-tts.md index aca9d7175c..97cc40aa10 100644 --- a/docs/spec/tts/remote-tts.md +++ b/docs/spec/tts/remote-tts.md @@ -97,8 +97,14 @@ iOS 向けの選択はアナウンスには効かない。将来 iOS で内蔵 T Google TTS の日本語ローカル音声のように `QUALITY_NORMAL` へ横並びになる端末では 優劣を判定できない。判断材料が尽きると識別子のアルファベット順で決まってしまい、 品質と無関係に `ja-jp-x-htm-local` が常に選ばれ、ユーザーが端末設定で選んだ音声も -無視される。これを避けるため `patches/expo-speech+57.0.1.patch` で -`android.speech.tts.Voice` の生の情報を JS へ渡している。 +無視される。これを避ける判断材料として、JS 側は `android.speech.tts.Voice` の +生の情報を表す次のフィールドを扱える。 + +ただし現在の `expo-speech` はこれらを返さない。以前は +`patches/expo-speech+57.0.1.patch` で返すようにしていたが、`expo-speech` は Android で +ビルド済みの AAR(`local-maven-repo`)から取り込まれるため、`node_modules` のソースへの +パッチはビルドに入っておらず、本番のバイナリにも含まれていなかった。現在のパッチには +含めていない。 | フィールド | 由来 | 用途 | | --- | --- | --- | @@ -126,10 +132,8 @@ Google TTS の日本語ローカル音声のように `QUALITY_NORMAL` へ横並 ログへ残す。端末ごとに音声のラインナップが違い、音質の問い合わせは実機でどの音声が 選ばれたかが分からないと切り分けられないため。 -生メタデータの露出は `expo-speech` へのネイティブ patch なので、反映には -ネイティブビルドが必要で OTA アップデートでは配信されない。patch が当たっていない -バイナリでは各フィールドが `undefined` となり、従来どおり識別子順のタイブレークへ -フォールバックする。 +フィールドが返らない現在は、Android でも各フィールドが `undefined` となり、 +地域一致の次は識別子ベースの判定と識別子順のタイブレークで決まる。 ## テキストの前処理 diff --git a/package.json b/package.json index c098d0d039..5a25a2fd41 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,13 @@ "react-native-watch-connectivity" ] } + }, + "autolinking": { + "android": { + "buildFromSource": [ + "expo-speech" + ] + } } }, "jest": { diff --git a/patches/expo-speech+57.0.1.patch b/patches/expo-speech+57.0.1.patch index 94d4c1a965..db9c3d70b0 100644 --- a/patches/expo-speech+57.0.1.patch +++ b/patches/expo-speech+57.0.1.patch @@ -1,157 +1,239 @@ diff --git a/node_modules/expo-speech/android/src/main/java/expo/modules/speech/SpeechModule.kt b/node_modules/expo-speech/android/src/main/java/expo/modules/speech/SpeechModule.kt -index 9ca869c..077cb21 100644 +index 9ca869c..d85cdc3 100644 --- a/node_modules/expo-speech/android/src/main/java/expo/modules/speech/SpeechModule.kt +++ b/node_modules/expo-speech/android/src/main/java/expo/modules/speech/SpeechModule.kt -@@ -4,6 +4,7 @@ import android.os.Bundle - import android.speech.tts.TextToSpeech - import android.speech.tts.UtteranceProgressListener - import android.speech.tts.Voice -+import android.util.Log - import expo.modules.kotlin.Promise +@@ -8,6 +8,7 @@ import expo.modules.kotlin.Promise import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition -@@ -17,6 +18,9 @@ const val speakingDoneEvent = "Exponent.speakingDone" - const val speakingStoppedEvent = "Exponent.speakingStopped" - const val speakingErrorEvent = "Exponent.speakingError" + import java.util.ArrayDeque ++import kotlinx.coroutines.launch + import java.util.Locale + import java.util.Queue -+// TrainLCD patch: voice / language 解決の診断ログ用タグ。 -+const val PATCH_LOG_TAG = "TrainLCDSpeech" -+ - class SpeechModule : Module() { - private val delayedUtterances: Queue = ArrayDeque() - private val delayedGetVoices: Queue = ArrayDeque() -@@ -83,6 +87,14 @@ class SpeechModule : Module() { - throw SpeechUnableToGetVoicesException(err) +@@ -36,12 +37,34 @@ class SpeechModule : Module() { + TextToSpeech.getMaxSpeechInputLength() } -+ // TrainLCD patch: 端末設定で選ばれているエンジン既定音声を JS 側へ伝える。 -+ // 一部エンジンは defaultVoice の取得で例外を投げるため防御する。 -+ val defaultVoiceName = try { -+ textToSpeech.defaultVoice?.name -+ } catch (_: Exception) { -+ null -+ } -+ - return nativeVoices.map { - val quality = if (it.quality > Voice.QUALITY_NORMAL) { - VoiceQuality.ENHANCED -@@ -90,11 +102,28 @@ class SpeechModule : Module() { - VoiceQuality.DEFAULT - } - -+ // TrainLCD patch: features / isNetworkConnectionRequired はエンジン実装に -+ // よっては例外を投げうるため、取得できない場合は保守的な既定値へ倒す。 -+ val networkRequired = try { -+ it.isNetworkConnectionRequired -+ } catch (_: Exception) { -+ false ++ // TrainLCD patch: 元の実装は shutdown したインスタンスを使い続けるため、Android の ++ // 「最近使ったアプリ」の一覧からアプリを消したあと、キャッシュに残ったプロセスで ++ // 開き直すと、発話がすべて "not bound to TTS engine" で失敗していた。破棄したら ++ // 参照を捨て、次に Activity が前面へ戻ってから作り直す。 ++ // JS 側の画面のアンマウントはこの通知より後に非同期で走るため、その間に届いた ++ // speak でエンジンを作り直すと、消したアプリが読み上げてしまう。前面へ戻るまでは ++ // 発話を受け付けない(expo-modules-core の AppContext.hostWasDestroyed と同じ区切り)。 ++ // TTS の状態は AsyncFunction と同じ modulesQueue のスレッドだけで扱い、ロックを使わない。 + OnActivityDestroys { +- textToSpeech.shutdown() ++ appContext.modulesQueue.launch { ++ hostDestroyed = true ++ _textToSpeech?.shutdown() ++ _textToSpeech = null ++ _ttsReady = false ++ stopDelayedUtterances() ++ resolveDelayedGetVoicesWithEmptyList() + } -+ val notInstalled = try { -+ it.features?.contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED) == true -+ } catch (_: Exception) { -+ false + } + ++ OnActivityEntersForeground { ++ appContext.modulesQueue.launch { ++ hostDestroyed = false + } ++ } + - VoiceRecord( - identifier = it.name, - name = it.name, - quality = quality, -- language = LanguageUtils.getISOCode(it.locale) -+ language = LanguageUtils.getISOCode(it.locale), -+ qualityScore = it.quality, -+ isDefault = defaultVoiceName != null && it.name == defaultVoiceName, -+ networkRequired = networkRequired, -+ notInstalled = notInstalled - ) ++ // TrainLCD patch: isSpeaking / stop のためだけにエンジンへ接続し直さない + AsyncFunction("isSpeaking") { +- textToSpeech.isSpeaking ++ _textToSpeech?.isSpeaking ?: false + } + + AsyncFunction("getVoices") { promise: Promise -> +@@ -56,7 +79,8 @@ class SpeechModule : Module() { + } + + AsyncFunction("stop") { +- textToSpeech.stop() ++ _textToSpeech?.stop() ++ Unit } - } -@@ -103,24 +132,48 @@ class SpeechModule : Module() { - options.pitch?.let(textToSpeech::setPitch) - options.rate?.let(textToSpeech::setSpeechRate) -- textToSpeech.language = options.language?.let { -- val locale = Locale(it) -- val languageAvailable = textToSpeech.isLanguageAvailable(locale) + AsyncFunction("speak") { id: String, text: String, options: SpeechOptions -> +@@ -64,7 +88,10 @@ class SpeechModule : Module() { + throw SpeechInputIsToLongException() + } + +- if (isTextToSpeechReady) { ++ if (hostDestroyed) { ++ // 受け付けなかったことを JS へ返し、完了待ちのまま残さない ++ sendEvent(speakingStoppedEvent, idToMap(id)) ++ } else if (isTextToSpeechReady) { + speakOut(id, text, options) + } else { + delayedUtterances.add(Utterance(id, text, options)) +@@ -143,50 +170,114 @@ class SpeechModule : Module() { + private val isTextToSpeechReady + get() = _ttsReady + +- private val textToSpeech: TextToSpeech by lazy { +- val newTtsInstance = TextToSpeech(appContext.reactContext) { status: Int -> +- if (status == TextToSpeech.SUCCESS) { +- // synchronize because in some cases this runs on another thread and _textToSpeech is null +- synchronized(this@SpeechModule) { +- _ttsReady = true +- _textToSpeech!!.setOnUtteranceProgressListener(object : UtteranceProgressListener() { - -- return@let if ( -- languageAvailable != TextToSpeech.LANG_MISSING_DATA && -- languageAvailable != TextToSpeech.LANG_NOT_SUPPORTED -- ) { -- locale -- } else { -- Locale.getDefault() -+ // TrainLCD patch: voice 指定がある場合は voice を最優先で適用する。 -+ // Voice は自身の Locale を持つため setLanguage は不要で、呼ぶとエンジンに -+ // よっては直後の setVoice と競合して直前の音声(別言語)のまま合成される -+ // ことがある。voice が見つからない場合のみ language から解決する。 -+ // 一部エンジンで getVoices が発話中に例外を投げることがあるため防御する。 -+ val matchedVoice = options.voice?.let { voiceName -> -+ try { -+ textToSpeech.voices?.firstOrNull { it.name == voiceName } -+ } catch (_: Exception) { -+ null +- override fun onStart(utteranceId: String) { +- sendEvent(speakingStartedEvent, idToMap(utteranceId)) +- } +- +- override fun onRangeStart(utteranceId: String, start: Int, end: Int, frame: Int) { +- val map = Bundle().apply { +- putString("id", utteranceId) +- putInt("charIndex", start) +- putInt("charLength", end - start) +- } +- sendEvent(speakingWillSayNextStringEvent, map) +- } +- +- override fun onDone(utteranceId: String) { +- sendEvent(speakingDoneEvent, idToMap(utteranceId)) +- } +- +- override fun onStop(utteranceId: String, interrupted: Boolean) { +- sendEvent(speakingStoppedEvent, idToMap(utteranceId)) +- } +- +- override fun onError(utteranceId: String) { +- sendEvent(speakingErrorEvent, idToMap(utteranceId)) +- } +- }) +- for ((id, text, options) in delayedUtterances) { +- speakOut(id, text, options) +- } +- for (promise in delayedGetVoices) { +- promise.resolve(getVoices()) +- } +- } ++ // TrainLCD patch: by lazy では shutdown 後に作り直せないため、破棄されていれば作り直す。 ++ // modulesQueue のスレッドからだけ呼ぶ ++ private val textToSpeech: TextToSpeech ++ get() = _textToSpeech ?: createTextToSpeech() ++ ++ private fun createTextToSpeech(): TextToSpeech { ++ lateinit var newTtsInstance: TextToSpeech ++ newTtsInstance = TextToSpeech(appContext.reactContext) { status: Int -> ++ // TrainLCD patch: この通知は TextToSpeech が内部のロックを持ったまま呼ぶ(メインスレッド、 ++ // バインダーのスレッド、またはコンストラクタの中)。ここでは状態に触れず modulesQueue へ回す ++ appContext.modulesQueue.launch { ++ onTextToSpeechInit(newTtsInstance, status) } -- } ?: Locale.getDefault() + } + _textToSpeech = newTtsInstance +- newTtsInstance ++ return newTtsInstance ++ } ++ ++ private fun onTextToSpeechInit(tts: TextToSpeech, status: Int) { ++ // 初期化を待つ間に破棄された古いインスタンス。接続が後から確立するので、ここで切る ++ if (tts !== _textToSpeech) { ++ tts.shutdown() ++ return ++ } ++ if (status != TextToSpeech.SUCCESS) { ++ // 捨てておけば、次の呼び出しで作り直す ++ tts.shutdown() ++ _textToSpeech = null ++ _ttsReady = false ++ failDelayedRequests() ++ return + } + -+ if (matchedVoice != null) { -+ textToSpeech.setVoice(matchedVoice) -+ } else { -+ // TrainLCD patch: Locale(String) は 'en-US' のような BCP 47 タグ全体を -+ // language として扱う不正な Locale を生成し、isLanguageAvailable が常に -+ // 失敗して Locale.getDefault()(端末既定言語)で合成されてしまう。 -+ // forLanguageTag で正しく解釈し、地域付きタグの音声データが無い端末では -+ // 言語のみの Locale へ段階的にフォールバックする。 -+ val resolvedLocale = options.language?.let { -+ val exactLocale = Locale.forLanguageTag(it.replace('_', '-')) -+ val languageOnlyLocale = Locale(exactLocale.language) ++ _ttsReady = true ++ tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() { + -+ val isUsable = { locale: Locale -> -+ val availability = textToSpeech.isLanguageAvailable(locale) -+ availability != TextToSpeech.LANG_MISSING_DATA && -+ availability != TextToSpeech.LANG_NOT_SUPPORTED ++ override fun onStart(utteranceId: String) { ++ sendEvent(speakingStartedEvent, idToMap(utteranceId)) ++ } ++ ++ override fun onRangeStart(utteranceId: String, start: Int, end: Int, frame: Int) { ++ val map = Bundle().apply { ++ putString("id", utteranceId) ++ putInt("charIndex", start) ++ putInt("charLength", end - start) + } ++ sendEvent(speakingWillSayNextStringEvent, map) ++ } ++ ++ override fun onDone(utteranceId: String) { ++ sendEvent(speakingDoneEvent, idToMap(utteranceId)) ++ } ++ ++ override fun onStop(utteranceId: String, interrupted: Boolean) { ++ sendEvent(speakingStoppedEvent, idToMap(utteranceId)) ++ } ++ ++ override fun onError(utteranceId: String) { ++ sendEvent(speakingErrorEvent, idToMap(utteranceId)) ++ } ++ }) ++ // 作り直したときに前の発話を流し直さないよう、取り出しながら消化する ++ if (hostDestroyed) { ++ stopDelayedUtterances() ++ } ++ // この消化は modulesQueue.launch の中で動き、例外を受け止める仕組みが無いとプロセスごと ++ // 落ちる。1 件ずつ受け止め、残りの要求を待たせたままにしない ++ while (true) { ++ val (id, text, options) = delayedUtterances.poll() ?: break ++ try { ++ speakOut(id, text, options) ++ } catch (e: Exception) { ++ sendEvent(speakingErrorEvent, idToMap(id)) ++ } ++ } ++ while (true) { ++ val promise = delayedGetVoices.poll() ?: break ++ try { ++ promise.resolve(getVoices()) ++ } catch (e: SpeechUnableToGetVoicesException) { ++ promise.reject(e) ++ } ++ } ++ } ++ ++ // 読み上げずに捨てる発話。JS 側が完了を待ち続けないよう停止として通知する ++ private fun stopDelayedUtterances() { ++ while (true) { ++ val utterance = delayedUtterances.poll() ?: break ++ sendEvent(speakingStoppedEvent, idToMap(utterance.id)) ++ } ++ } ++ ++ // 初期化に失敗した回の要求を待たせたままにしない。上流 58.0.0 の #49228 と同じく、 ++ // 発話はエラーとして通知し、音声一覧は空で返す ++ private fun failDelayedRequests() { ++ while (true) { ++ val utterance = delayedUtterances.poll() ?: break ++ sendEvent(speakingErrorEvent, idToMap(utterance.id)) ++ } ++ resolveDelayedGetVoicesWithEmptyList() ++ } ++ ++ private fun resolveDelayedGetVoicesWithEmptyList() { ++ while (true) { ++ val promise = delayedGetVoices.poll() ?: break ++ promise.resolve(emptyList()) ++ } + } -- options.voice?.let { voiceName -> -- textToSpeech.voices -- .firstOrNull { it.name == voiceName } -- ?.let(textToSpeech::setVoice) -+ when { -+ isUsable(exactLocale) -> exactLocale -+ isUsable(languageOnlyLocale) -> languageOnlyLocale -+ else -> Locale.getDefault() -+ } -+ } ?: Locale.getDefault() -+ val languageResult = textToSpeech.setLanguage(resolvedLocale) -+ Log.d( -+ PATCH_LOG_TAG, -+ "speak id=$id engine=${textToSpeech.defaultEngine} requestedVoice=${options.voice} requestedLanguage=${options.language} setLanguage=$resolvedLocale result=$languageResult" -+ ) - } + // Helpers +@@ -200,7 +291,10 @@ class SpeechModule : Module() { + val options: SpeechOptions + ) - val params = Bundle().apply { -diff --git a/node_modules/expo-speech/android/src/main/java/expo/modules/speech/VoiceRecord.kt b/node_modules/expo-speech/android/src/main/java/expo/modules/speech/VoiceRecord.kt -index 480ce46..e85ad12 100644 ---- a/node_modules/expo-speech/android/src/main/java/expo/modules/speech/VoiceRecord.kt -+++ b/node_modules/expo-speech/android/src/main/java/expo/modules/speech/VoiceRecord.kt -@@ -15,5 +15,19 @@ data class VoiceRecord( - @Field val identifier: String, - @Field val name: String, - @Field val quality: VoiceQuality, -- @Field val language: String -+ @Field val language: String, -+ // TrainLCD patch: 音声選択の精度を上げるための Android 固有メタデータ。 -+ // quality は ENHANCED / DEFAULT の 2 値へ丸められており、ローカル音声同士の -+ // 優劣を判定できない(Google TTS の日本語ローカル音声は軒並み QUALITY_NORMAL -+ // に並ぶ)。android.speech.tts.Voice が持つ生の情報を併せて渡すことで、 -+ // JS 側が識別子のアルファベット順という無根拠なタイブレークに頼らずに済む。 -+ // -+ // - qualityScore: Voice.getQuality() の生値 (QUALITY_VERY_LOW=100 〜 VERY_HIGH=500) -+ // - isDefault: 端末設定で選ばれているエンジン既定音声か -+ // - networkRequired: 合成にネットワーク接続が必要か(識別子の 'network' 判定より正確) -+ // - notInstalled: 音声データが未ダウンロードで、指定しても合成できない状態か -+ @Field val qualityScore: Int, -+ @Field val isDefault: Boolean, -+ @Field val networkRequired: Boolean, -+ @Field val notInstalled: Boolean - ) : Record +- // do not refer to these - they're only needed when initializing `textToSpeech` ++ // TrainLCD patch: 以下の状態と待ち行列は modulesQueue のスレッドだけで読み書きする + private var _textToSpeech: TextToSpeech? = null + private var _ttsReady = false ++ ++ // TrainLCD patch: Activity が破棄されてから次に前面へ戻るまでの間か ++ private var hostDestroyed = false + } diff --git a/src/utils/nativeTtsVoice.ts b/src/utils/nativeTtsVoice.ts index 517fe11e42..07d3347539 100644 --- a/src/utils/nativeTtsVoice.ts +++ b/src/utils/nativeTtsVoice.ts @@ -24,10 +24,10 @@ import { type Voice, VoiceQuality } from 'expo-speech'; // QUALITY_NORMAL へ並ぶため、expo-speech が返す 2 値の quality だけでは優劣を // 判定できず、識別子のアルファベット順という無根拠なタイブレークで音声が決まって // しまう('ja-jp-x-htm-local' が常に勝ち、ユーザーが端末設定で選んだ音声も無視 -// される)。そのため expo-speech の Android 実装へ patch を当てて生の -// Voice メタデータ(qualityScore / isDefault / networkRequired / notInstalled) -// を受け取り、それらを優先順の判断材料にする。iOS ではこれらのフィールドは -// 返らないため、従来どおり識別子と quality で判定する。 +// される)。生の Voice メタデータ(qualityScore / isDefault / networkRequired / +// notInstalled)が届けば優先順の判断材料にするが、現在の expo-speech はどの +// プラットフォームでも返さないため、識別子と quality で判定することになる +// (docs/spec/tts/remote-tts.md「端末内蔵 TTS の音声選択」)。 // // 注意: expo-speech の iOS 実装は AVSpeechSynthesisVoiceQuality.premium を // "Default" として報告する(native 側が enhanced 以外を一律 Default に落とす)ため、 @@ -35,8 +35,8 @@ import { type Voice, VoiceQuality } from 'expo-speech'; // `com.apple.voice.premium.ja-JP.Kyoko` / `com.apple.ttsbundle.Kyoko-premium` の // ように品質を含む命名になっているので、識別子でも判定して補完する。 -// expo-speech の Voice 型は Android patch で追加したフィールドを含まないため、 -// アプリ側で拡張して扱う。iOS ではいずれも返らないので optional にしている。 +// expo-speech の Voice 型はこれらのフィールドを含まないため、アプリ側で拡張して扱う。 +// 現在はどのプラットフォームでも返らないので optional にしている。 export type NativeVoice = Voice & { // android.speech.tts.Voice.getQuality() の生値 (VERY_LOW=100 〜 VERY_HIGH=500) qualityScore?: number; From 8ada50315f4388f0bf77794e11f733ffea08ca22 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:47:56 +0900 Subject: [PATCH 15/17] Bump version for canary release (#7187) --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2336c9da28..5b2f9343eb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,12 +170,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000847 + versionCode 100000849 versionName "10.18.0" } prod { dimension "environment" - versionCode 100000847 + versionCode 100000849 versionName "10.18.0" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -186,7 +186,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000847 + versionCode 100000849 versionName "10.18.0" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index acd4fbdb73..4e78efbf4f 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000848 + versionCode = 100000850 versionName = "10.18.0" } create("prod") { dimension = "environment" - versionCode = 100000848 + versionCode = 100000850 versionName = "10.18.0" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000848 + versionCode = 100000850 versionName = "10.18.0" } } diff --git a/app.config.ts b/app.config.ts index 4dadb2fd48..c7e64f7728 100644 --- a/app.config.ts +++ b/app.config.ts @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2953', + buildNumber: '2954', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000847, + versionCode: 100000849, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index fe1ae24d7e..359eea2f40 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2953; + CURRENT_PROJECT_VERSION = 2954; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; From 89912a2959ee35e1edcc8581a6ec42d592b97f0a Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 18:49:22 +0900 Subject: [PATCH 16/17] =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E3=81=AB?= =?UTF-8?q?=E5=85=A5=E3=81=A3=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9Fexpo-task-manager=E3=81=AE=E3=83=91=E3=83=83=E3=83=81?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4=20(#7185)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 31 +++++++++++++++++++++++++ patches/expo-task-manager+57.0.13.patch | 29 ----------------------- 2 files changed, 31 insertions(+), 29 deletions(-) delete mode 100644 patches/expo-task-manager+57.0.13.patch diff --git a/docs/changelog.md b/docs/changelog.md index 3a46c5c9c3..401d966889 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,37 @@ CLAUDE.md「Security & Configuration Guardrails」に従い、依存更新後に 新しいエントリを上に追加する。 +## 2026-10-05 — ビルドに入っていなかった expo-task-manager のパッチを削除 + +対象バージョン: v10.18.0 時点の `dev` + +### 内容 + +- `patches/expo-task-manager+57.0.13.patch` を削除した。`TaskManagerUtils.createJobInfo` の API 31+ 分岐で + `setExpedited(true)` を `setMinimumLatency(0)` と `setOverrideDeadline` に置き換えるパッチだった。 +- `expo-task-manager` はビルド済みの AAR(`local-maven-repo`)を同梱し、`expo-module.config.json` に `publication` を持つ。 + `buildFromSource` に入っていないため、Gradle は AAR を使い、`node_modules` のソースへのパッチはビルドに入らない。 + このパッチを作った時点の 14.0.9 から 57.0.13 まで、どの版も同じ構成だった。 +- #5163 で `app.json` の `expo.android.autolinking.buildFromSource` に `expo-task-manager` を加えていた。 + `expo-modules-autolinking` が読むのは `package.json` の `expo.autolinking` だけなので、この設定は使われていなかった。 + その後 #5173 で `app.config.ts` へ移行したときに設定自体もなくなっている。 +- ストア版の本番 APK(v10.18.0)を逆アセンブルし、`createJobInfo` が上流のまま(API 31+ は `setExpedited(true)`)であることを確かめた。 + パッチを消しても、ビルドに入るコードは変わらない。 + +### 確認 + +- `./gradlew :app:dependencies --configuration prodReleaseRuntimeClasspath` で、 + `expo-task-manager` が `host.exp.exponent:expo.modules.taskmanager:57.0.13`(AAR)として解決されることを確かめた。 +- Sentry の直近 90 日間で、このパッチが対象にしていた `JobInfo.Builder.build()` の + `You're trying to build a job with no constraints` は 0 件だった。 + +### 学び + +- Expo のモジュールへのネイティブのパッチは、`package.json` の `expo.autolinking.android.buildFromSource` に加えないとビルドに入らない。 + `app.json` に書いても読まれない。 +- 「2026-08-26 — Expo SDK 57 系の依存を推奨バージョンへ更新」の項にある、このパッチの挙動の説明は、 + パッチがビルドに入っていた前提で書かれている。実際の出荷物は上流の実装のままだった。 + ## 2026-10-05 — Android で一覧から消して開き直すと端末内蔵 TTS が鳴らない不具合を修正 対象バージョン: v10.18.0 の本番で発生 diff --git a/patches/expo-task-manager+57.0.13.patch b/patches/expo-task-manager+57.0.13.patch deleted file mode 100644 index c753e71731..0000000000 --- a/patches/expo-task-manager+57.0.13.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java b/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java -index 82bc0db..905c385 100644 ---- a/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java -+++ b/node_modules/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerUtils.java -@@ -214,8 +214,22 @@ public class TaskManagerUtils implements TaskManagerUtilsInterface { - jobBuilder.setImportantWhileForeground(true) - .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE); - } else { -- // For Android 12 (API 31) and above -- jobBuilder.setExpedited(true); -+ // For Android 12 (API 31) and above. -+ // setExpedited(true) alone causes JobInfo.Builder.build() to throw -+ // IllegalArgumentException("You're trying to build a job with no constraints, -+ // this is not allowed.") on Android 14+/16 because expedited jobs do not -+ // register an early/late/functional constraint, and the build-time validator -+ // counts none of setPersisted/setRequiresDeviceIdle(false) as a constraint. -+ // We apply the minimum-latency + override-deadline fallback uniformly across -+ // API 31+ rather than gating on SDK_INT >= 36, because OEM/patch-level -+ // variance on Android 14 and 15 can still trip the same crash. -+ // Note on the trade-off: the JS-side watchPositionAsync bypass is gated by -+ // NEEDS_JOBSCHEDULER_BYPASS (src/constants/native.ts), which is true only on -+ // API 36+, so it does NOT offset the loss of expedited priority on API 31-35. -+ // There, the Android foreground location service keeping the process alive is -+ // the only mitigation, at the cost of a possible JS delivery delay under Doze. -+ jobBuilder.setMinimumLatency(0) -+ .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE); - } - - return jobBuilder.build(); From b7a70f7d540801c46899b33bf1772524f4217c3a Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Mon, 5 Oct 2026 18:58:39 +0900 Subject: [PATCH 17/17] =?UTF-8?q?v10.18.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 Co-Authored-By: Claude Opus 5.5 --- android/app/build.gradle | 12 +++---- android/wearable/build.gradle.kts | 12 +++---- app.config.ts | 6 ++-- ios/TrainLCD.xcodeproj/project.pbxproj | 44 +++++++++++++------------- package-lock.json | 4 +-- package.json | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 5b2f9343eb..42509db7a3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -170,13 +170,13 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000849 - versionName "10.18.0" + versionCode 100000851 + versionName "10.18.1" } prod { dimension "environment" - versionCode 100000849 - versionName "10.18.0" + versionCode 100000851 + versionName "10.18.1" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 // ストアの Canary 版を消さずに同居させるためだけに存在し、配信には使わない @@ -186,8 +186,8 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000849 - versionName "10.18.0" + versionCode 100000851 + versionName "10.18.1" } } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index 4e78efbf4f..afa2f42740 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,13 +85,13 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000850 - versionName = "10.18.0" + versionCode = 100000852 + versionName = "10.18.1" } create("prod") { dimension = "environment" - versionCode = 100000850 - versionName = "10.18.0" + versionCode = 100000852 + versionName = "10.18.1" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は // applicationId が一致する場合しか疎通しないため、:app を .local にしたときは @@ -100,8 +100,8 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000850 - versionName = "10.18.0" + versionCode = 100000852 + versionName = "10.18.1" } } diff --git a/app.config.ts b/app.config.ts index c7e64f7728..3b55ebd914 100644 --- a/app.config.ts +++ b/app.config.ts @@ -8,7 +8,7 @@ const IS_DEV = process.env.APP_VARIANT === 'dev' || IS_LOCAL; export default { name: 'TrainLCD', slug: 'trainlcd', - version: '10.18.0', + version: '10.18.1', plugins: [ 'expo-image', 'expo-font', @@ -64,7 +64,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2954', + buildNumber: '2955', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -78,7 +78,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000849, + versionCode: 100000851, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 359eea2f40..7370be417c 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2562,7 +2562,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2602,7 +2602,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2661,7 +2661,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2718,7 +2718,7 @@ "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(inherited)\"", ); - MARKETING_VERSION = 10.18.0; + MARKETING_VERSION = 10.18.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2821,7 +2821,7 @@ "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", "\"$(inherited)\"", ); - MARKETING_VERSION = 10.18.0; + MARKETING_VERSION = 10.18.1; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; @@ -2849,7 +2849,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2889,7 +2889,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3101,7 +3101,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3153,7 +3153,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3204,7 +3204,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3262,7 +3262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3313,7 +3313,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3370,7 +3370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3419,7 +3419,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3471,7 +3471,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3690,7 +3690,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3746,7 +3746,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3796,7 +3796,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3820,7 +3820,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 10.18.0; + MARKETING_VERSION = 10.18.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3854,7 +3854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2954; + CURRENT_PROJECT_VERSION = 2955; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3874,7 +3874,7 @@ "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 10.18.0; + MARKETING_VERSION = 10.18.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 a1bf3fd307..0fb413a988 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trainlcd", - "version": "10.18.0", + "version": "10.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trainlcd", - "version": "10.18.0", + "version": "10.18.1", "hasInstallScript": true, "dependencies": { "@expo-google-fonts/roboto": "^0.2.3", diff --git a/package.json b/package.json index 5a25a2fd41..4579133110 100644 --- a/package.json +++ b/package.json @@ -185,5 +185,5 @@ } }, "name": "trainlcd", - "version": "10.18.0" + "version": "10.18.1" }