From bfc77a10d18bc3e3617a18973e97b4e8c399c792 Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Sat, 28 Mar 2026 17:47:46 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E5=B0=8F=E7=94=B0=E6=80=A5=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E3=81=AETypeChangeNotify=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=81=A8=E7=9B=B4=E9=80=9A=E9=81=8B=E8=BB=A2=E6=99=82=E3=81=AE?= =?UTF-8?q?=E8=B7=AF=E7=B7=9A=E5=90=8D=E8=A1=A8=E7=A4=BA=E3=83=90=E3=82=B0?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#5706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 小田急テーマのTypeChangeNotifyに独自グラデーションデザインを追加 Co-Authored-By: Claude Opus 4.6 (1M context) * 直通運転時のTypeChangeNotifyで中間路線名が正しく表示されないバグを修正 Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- src/components/TypeChangeNotify.test.tsx | 162 +++++++++++ src/components/TypeChangeNotify.tsx | 328 ++++++++++++++++++++++- 2 files changed, 486 insertions(+), 4 deletions(-) diff --git a/src/components/TypeChangeNotify.test.tsx b/src/components/TypeChangeNotify.test.tsx index 430e46864..6d4339b1e 100644 --- a/src/components/TypeChangeNotify.test.tsx +++ b/src/components/TypeChangeNotify.test.tsx @@ -76,6 +76,10 @@ jest.mock('./BarTerminalEast', () => ({ BarTerminalEast: jest.fn(() => null), })); +jest.mock('./BarTerminalOdakyu', () => ({ + BarTerminalOdakyu: jest.fn(() => null), +})); + jest.mock('./BarTerminalSaikyo', () => ({ BarTerminalSaikyo: jest.fn(() => null), })); @@ -149,4 +153,162 @@ describe('TypeChangeNotify', () => { render(); }).not.toThrow(); }); + + it('ODAKYUテーマでクラッシュしない', () => { + const { useAtomValue } = require('jotai'); + useAtomValue.mockImplementation((atom: unknown) => { + if (atom === require('../store/atoms/station').default) { + return { + selectedDirection: 'INBOUND', + stations: [], + selectedBound: null, + }; + } + if (atom === require('../store/atoms/theme').themeAtom) { + return 'ODAKYU'; + } + return {}; + }); + + expect(() => { + render(); + }).not.toThrow(); + }); + + it('直通運転時に中間路線名が正しく表示される(小田急多摩線→千代田線→常磐線)', () => { + const { useAtomValue } = require('jotai'); + const { + useCurrentLine, + useCurrentStation, + useCurrentTrainType, + useNextTrainType, + } = require('~/hooks'); + + const odakyuTamaLine = { + id: 100, + nameShort: '小田急多摩線', + nameRoman: 'Odakyu Tama Line', + color: '#0D82C7', + }; + + const chiyodaLine = { + id: 200, + nameShort: '千代田線', + nameRoman: 'Chiyoda Line', + color: '#009944', + }; + + const jobanLine = { + id: 300, + nameShort: '常磐線', + nameRoman: 'Joban Line', + color: '#00B264', + }; + + // 直通運転時、station.lineは全て選択路線(小田急多摩線)になるが、 + // station.linesには実際の路線が含まれる + const stations = [ + { + id: 1, + groupId: 1, + name: '新百合ヶ丘', + nameRoman: 'Shin-Yurigaoka', + line: odakyuTamaLine, + lines: [odakyuTamaLine], + trainType: { typeId: 1, name: '急行', nameRoman: 'Express' }, + stopCondition: 'STOP', + }, + { + id: 2, + groupId: 2, + name: '代々木上原', + nameRoman: 'Yoyogi-Uehara', + line: odakyuTamaLine, + lines: [odakyuTamaLine, chiyodaLine], + trainType: { typeId: 2, name: '準急', nameRoman: 'Semi Express' }, + stopCondition: 'STOP', + }, + { + id: 3, + groupId: 3, + name: '表参道', + nameRoman: 'Omote-sando', + line: odakyuTamaLine, + lines: [chiyodaLine], + trainType: { typeId: 2, name: '準急', nameRoman: 'Semi Express' }, + stopCondition: 'STOP', + }, + { + id: 4, + groupId: 4, + name: '綾瀬', + nameRoman: 'Ayase', + line: odakyuTamaLine, + lines: [chiyodaLine, jobanLine], + trainType: { typeId: 2, name: '準急', nameRoman: 'Semi Express' }, + stopCondition: 'STOP', + }, + { + id: 5, + groupId: 4, + name: '綾瀬', + nameRoman: 'Ayase', + line: jobanLine, + lines: [chiyodaLine, jobanLine], + trainType: { typeId: 3, name: '各停', nameRoman: 'Local' }, + stopCondition: 'STOP', + }, + { + id: 6, + groupId: 5, + name: '取手', + nameRoman: 'Toride', + line: jobanLine, + lines: [jobanLine], + trainType: { typeId: 3, name: '各停', nameRoman: 'Local' }, + stopCondition: 'STOP', + }, + ]; + + useCurrentLine.mockReturnValue(odakyuTamaLine); + useCurrentStation.mockReturnValue(stations[3]); + useCurrentTrainType.mockReturnValue({ + typeId: 2, + name: '準急', + nameRoman: 'Semi Express', + color: '#009944', + line: odakyuTamaLine, + }); + useNextTrainType.mockReturnValue({ + typeId: 3, + name: '各停', + nameRoman: 'Local', + color: '#00B264', + line: jobanLine, + }); + + useAtomValue.mockImplementation((atom: unknown) => { + if (atom === require('../store/atoms/station').default) { + return { + selectedDirection: 'INBOUND', + stations, + selectedBound: { name: '取手', nameRoman: 'Toride' }, + }; + } + if (atom === require('../store/atoms/theme').themeAtom) { + return 'TOKYO_METRO'; + } + return {}; + }); + + const { queryAllByText } = render(); + + // 左側のバーの路線名が千代田線(中間路線)であること + // 小田急多摩線(選択路線)ではないこと + const chiyodaTexts = queryAllByText(/千代田線/); + expect(chiyodaTexts.length).toBeGreaterThan(0); + + const odakyuTexts = queryAllByText(/小田急多摩線/); + expect(odakyuTexts).toHaveLength(0); + }); }); diff --git a/src/components/TypeChangeNotify.tsx b/src/components/TypeChangeNotify.tsx index 1edf1bfac..a9a706e30 100644 --- a/src/components/TypeChangeNotify.tsx +++ b/src/components/TypeChangeNotify.tsx @@ -18,6 +18,7 @@ import { themeAtom } from '../store/atoms/theme'; import isTablet from '../utils/isTablet'; import truncateTrainType from '../utils/truncateTrainType'; import { BarTerminalEast } from './BarTerminalEast'; +import { BarTerminalOdakyu } from './BarTerminalOdakyu'; import { BarTerminalSaikyo } from './BarTerminalSaikyo'; import Typography from './Typography'; @@ -419,6 +420,289 @@ const EastBars = React.memo(function EastBars({ ); }); +const ODAKYU_HIGHLIGHT_OFFSET = 0.35; + +const odakyuBarGradient: ColorGradientFn = (color) => [ + `${color}ff`, + `${color}bb`, +]; +const odakyuBoxGradient: ColorGradientFn = (color) => [ + `${color}ee`, + `${color}aa`, +]; + +// BarTerminalOdakyu の viewBox(24x48) に合わせたアスペクト比 +const odakyuTerminalWidth = barHeight / 2; + +const OdakyuBars = React.memo(function OdakyuBars({ + currentLine, + nextLine, + trainType, + nextTrainType, +}: { + currentLine: Line; + nextLine: Line; + trainType: TrainType; + nextTrainType: TrainType; +}) { + const dim = useWindowDimensions(); + const barWidth = useBarWidth(); + const rightBarWidth = Math.max(0, barWidth - odakyuTerminalWidth); + + if (!trainType || !nextTrainType) { + return null; + } + + const leftColor = (nextLine ? currentLine : trainType)?.color ?? '#000000'; + const rightColor = (nextLine ?? nextTrainType)?.color ?? '#000000'; + + return ( + + {/* Current line - background */} + + + {/* Current line - color overlay */} + + + {/* Current line - shadow */} + + {/* Current line - gloss */} + + + + + {/* Next line - background */} + + + {/* Next line - color overlay */} + + + {/* Next line - shadow */} + + {/* Next line - gloss */} + + + + {/* Train type boxes */} + + + + {/* Box shadow */} + + {/* Box gloss */} + + + + + {(trainType.name ?? '') + .replace('\n', '') + .replace(parenthesisRegexp, '')} + + + {truncateTrainType( + (trainType.nameRoman ?? '') + .replace('\n', '') + .replace(parenthesisRegexp, '') + )} + + + {nextLine && ( + + {/* eslint-disable-next-line react/jsx-one-expression-per-line */} + {(currentLine?.nameShort ?? '').replace(parenthesisRegexp, '')}{' '} + {(currentLine?.nameRoman ?? '').replace(parenthesisRegexp, '')} + + )} + + + + + {/* Box shadow */} + + {/* Box gloss */} + + + + + {(nextTrainType.name ?? '') + .replace('\n', '') + .replace(parenthesisRegexp, '')} + + + {truncateTrainType( + (nextTrainType.nameRoman ?? '') + .replace('\n', '') + .replace(parenthesisRegexp, '') + )} + + + {nextLine && ( + + {/* eslint-disable-next-line react/jsx-one-expression-per-line */} + {(nextLine.nameShort ?? '').replace(parenthesisRegexp, '')}{' '} + {(nextLine.nameRoman ?? '').replace(parenthesisRegexp, '')} + + )} + + + ); +}); + const SaikyoBars = React.memo(function SaikyoBars({ currentLine, nextLine, @@ -989,6 +1273,26 @@ const TypeChangeNotify: React.FC = ({ stations, ]); + // バー表示用: 現在の種別の駅の.linesから、選択路線(currentLine)でもnextLineでもない路線を探す + // 例: 小田急多摩線→千代田線→常磐線の場合、.linesから千代田線を取得する + const displayCurrentLine = useMemo(() => { + if (!nextLine) { + return currentLine; + } + for (const s of stations) { + if (s.trainType?.typeId !== trainType?.typeId) { + continue; + } + const found = s.lines?.find( + (l) => l.id !== nextLine.id && l.id !== currentLine?.id + ); + if (found) { + return found as Line; + } + } + return currentLine; + }, [stations, trainType, nextLine, currentLine]); + const aOrAn = useMemo(() => { if (!nextTrainType || !trainType) { return ''; @@ -1051,7 +1355,13 @@ const TypeChangeNotify: React.FC = ({ ]); const BarsComponent = useCallback(() => { - if (!currentLine || !nextLine || !trainType || !nextTrainType) { + if ( + !currentLine || + !displayCurrentLine || + !nextLine || + !trainType || + !nextTrainType + ) { return null; } @@ -1059,7 +1369,7 @@ const TypeChangeNotify: React.FC = ({ case 'SAIKYO': return ( = ({ case 'JL': return ( + ); + case 'ODAKYU': + return ( + = ({ default: return ( = ({ } }, [ currentLine, + displayCurrentLine, nextLine, trainType, nextTrainType, From 211d2ad6834d21cbca6a936223104abf64296e31 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:53:07 +0900 Subject: [PATCH 2/2] Bump version for canary release (#5708) Co-authored-by: TinyKitten <32848922+TinyKitten@users.noreply.github.com> --- android/app/build.gradle | 4 +-- app.config.ts | 5 ++-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9ceba4d60..4f415fa40 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -141,12 +141,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000352 + versionCode 100000353 versionName "10.3.1" } prod { dimension "environment" - versionCode 100000352 + versionCode 100000353 versionName "10.3.1" } } diff --git a/app.config.ts b/app.config.ts index e27e2ec84..d801184f7 100644 --- a/app.config.ts +++ b/app.config.ts @@ -53,7 +53,7 @@ export default ({ config }: ConfigContext) => ({ }, }, ios: { - buildNumber: '2565', + buildNumber: '2566', bundleIdentifier: process.env.EAS_BUILD_PROFILE === 'production' ? 'me.tinykitten.trainlcd' @@ -70,7 +70,7 @@ export default ({ config }: ConfigContext) => ({ ? 'me.tinykitten.trainlcd' : 'me.tinykitten.trainlcd.dev', permissions: [], - versionCode: 100000352, + versionCode: 100000353, }, owner: 'trainlcd', }); @@ -128,5 +128,6 @@ export default ({ config }: ConfigContext) => ({ + diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 750308c1b..0a19a5cb7 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2419,7 +2419,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2458,7 +2458,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2517,7 +2517,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2623,7 +2623,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2702,7 +2702,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2741,7 +2741,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2952,7 +2952,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3003,7 +3003,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3054,7 +3054,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3112,7 +3112,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3163,7 +3163,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3220,7 +3220,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3268,7 +3268,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3319,7 +3319,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3538,7 +3538,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3594,7 +3594,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3644,7 +3644,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3702,7 +3702,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2565; + CURRENT_PROJECT_VERSION = 2566; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO;