diff --git a/apps/dotto/lib/feature/bus/bus_screen.dart b/apps/dotto/lib/feature/bus/bus_screen.dart index c4aaee09..99d91a75 100644 --- a/apps/dotto/lib/feature/bus/bus_screen.dart +++ b/apps/dotto/lib/feature/bus/bus_screen.dart @@ -183,7 +183,9 @@ final class BusScreen extends HookConsumerWidget { isTo: value.isTo, isKameda: kameda, myBusStopName: value.myBusStop.name, - onTap: busTrip.route == '0' + delayMinutes: busTrip.delayMinutes, + isCancelled: busTrip.isCancelled, + onTap: busTrip.route == '0' || busTrip.isCancelled ? null : () async { await Navigator.of(context).push( @@ -348,6 +350,8 @@ final class _BusTripTile extends StatelessWidget { required this.isTo, required this.isKameda, required this.myBusStopName, + this.delayMinutes, + this.isCancelled = false, this.onTap, super.key, }); @@ -358,6 +362,8 @@ final class _BusTripTile extends StatelessWidget { final bool isTo; final bool isKameda; final String myBusStopName; + final int? delayMinutes; + final bool isCancelled; final VoidCallback? onTap; BusLandmark _busType() { @@ -398,8 +404,49 @@ final class _BusTripTile extends StatelessWidget { children: [ Text( '${formatDuration(beginTime)} → ${formatDuration(endTime)}', - style: Theme.of(context).textTheme.headlineSmall, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: isCancelled + ? SemanticColor.light.labelSecondary + : null, + decoration: isCancelled ? TextDecoration.lineThrough : null, + ), ), + if (isCancelled) + Row( + spacing: 4, + children: [ + Icon( + Icons.block, + size: 16, + color: SemanticColor.light.accentError, + ), + Text( + '運休', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: SemanticColor.light.accentError, + fontWeight: FontWeight.w600, + ), + ), + ], + ) + else if (delayMinutes != null && delayMinutes! > 0) + Row( + spacing: 4, + children: [ + Icon( + Icons.schedule, + size: 16, + color: SemanticColor.light.accentWarning, + ), + Text( + '$delayMinutes分遅延見込み', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: SemanticColor.light.accentWarning, + fontWeight: FontWeight.w600, + ), + ), + ], + ), Text( directionText.isEmpty ? route : '$route $directionText', style: Theme.of(context).textTheme.bodySmall?.copyWith( diff --git a/apps/dotto/lib/repository/model/bus_trip.dart b/apps/dotto/lib/repository/model/bus_trip.dart index d02aea02..dcea6f35 100644 --- a/apps/dotto/lib/repository/model/bus_trip.dart +++ b/apps/dotto/lib/repository/model/bus_trip.dart @@ -1,14 +1,24 @@ +import 'dart:math'; + import 'package:dotto/repository/model/bus_stop.dart'; import 'package:dotto/repository/model/bus_trip_stop.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'bus_trip.freezed.dart'; +final Random _mockRandom = Random(); + +int _mockDelayMinutes() => _mockRandom.nextInt(9); + +bool _mockIsCancelled() => _mockRandom.nextInt(10) == 0; + @freezed abstract class BusTrip with _$BusTrip { const factory BusTrip({ required String route, required List stops, + int? delayMinutes, + @Default(false) bool isCancelled, }) = _BusTrip; factory BusTrip.fromFirebase( @@ -17,6 +27,7 @@ abstract class BusTrip with _$BusTrip { ) { final stopsList = map['stops'] as List; final busStopById = {for (final stop in allStops) stop.id: stop}; + final cancelled = _mockIsCancelled(); return BusTrip( route: map['route'] as String, stops: stopsList.map((e) { @@ -28,6 +39,8 @@ abstract class BusTrip with _$BusTrip { } return BusTripStop.fromFirebase(targetBusStop, stopMap); }).toList(), + delayMinutes: cancelled ? null : _mockDelayMinutes(), + isCancelled: cancelled, ); } }