Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
vareversat committed Mar 11, 2024
1 parent da02b69 commit 2729f02
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 280 deletions.
220 changes: 115 additions & 105 deletions lib/bloc/forecast/forecast_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:chabo_app/const.dart';
import 'package:chabo_app/models/abstract_forecast.dart';
import 'package:chabo_app/models/boat_forecast.dart';
import 'package:chabo_app/models/maintenance_forecast.dart';
import 'package:chabo_app/models/special_event_forecast.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Expand All @@ -17,112 +18,134 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
final SentryHttpClient httpClient;

ForecastBloc({required this.httpClient}) : super(const ForecastState()) {
Timer.periodic(const Duration(seconds: 1), _onRefreshCurrentStatus);
Timer.periodic(const Duration(seconds: 1), _onAutomaticTimerRefresh);
on<ForecastFetched>(
_onForecastFetched,
);
}

void _onRefreshCurrentStatus(Timer timer) {
void _onAutomaticTimerRefresh(Timer timer) async {
try {
if (state.status == ForecastStatus.success) {
final currentStatus = _getCurrentStatus(state.forecasts);
final previousStatus =
_getPreviousStatus(state.forecasts, currentStatus);
if (currentStatus != state.currentForecast &&
currentStatus != previousStatus) {
// ignore: invalid_use_of_visible_for_testing_member
emit(
state.copyWith(
currentForecast: currentStatus,
previousForecast: previousStatus,
),
);
var bridgeStatus = BridgeStatus.bridgeClosed;
var forecastStatus = ForecastStatus.forecastsFetched;
var currentForecast = state.currentForecast;
var nextForecast = state.nextForecast;
var forecasts = state.forecasts;
if (currentForecast == null) {
bridgeStatus = BridgeStatus.bridgeOpen;
if (nextForecast == null) {
forecastStatus = ForecastStatus.forecastsYetToBeAnnounced;
} else {
if (nextForecast.circulationClosingDate.isBefore(DateTime.now())) {
bridgeStatus = BridgeStatus.bridgeOpen;
currentForecast = nextForecast;
nextForecast = await _fetchNextForecast();
forecasts = await _fetchForecasts();
}
}
} else {
if (currentForecast.circulationReOpeningDate.isAfter(DateTime.now())) {
bridgeStatus = BridgeStatus.bridgeOpen;
currentForecast = null;
}
}
// ignore: invalid_use_of_visible_for_testing_member
emit(state.copyWith(
bridgeStatus: bridgeStatus,
forecastStatus: forecastStatus,
currentForecast: currentForecast,
nextForecast: nextForecast,
forecasts: forecasts,
errorMessage: DateTime.now().toIso8601String()));
} catch (_) {
// ignore: invalid_use_of_visible_for_testing_member
emit(state.copyWith(
status: ForecastStatus.failure,
message: _.toString(),
forecastStatus: ForecastStatus.error,
errorMessage: _.toString(),
));
}
}

Future<List<AbstractForecast>> _fetchForecasts(
int offset,
) async {
Future<List<AbstractForecast>> _fetchForecasts() async {
var uri = Uri.https(
'opendata.bordeaux-metropole.fr',
'/api/records/1.0/search',
'beta.chabo-api.vareversat.fr',
'/v1/forecasts',
<String, String>{
'dataset': 'previsions_pont_chaban',
'rows': '${Const.forecastLimit}',
'sort': '-date_passage',
'start': '$offset',
'timezone': 'Europe/Paris',
'limit': '10',
'offset': '0',
'from': DateTime.now().toUtc().toIso8601String()
},
);
final response = await httpClient.get(uri);
final response = await httpClient.get(uri, headers: {
'Timezone': 'UTC',
'User-Agent': 'https://github.com/vareversat/chabo-app'
});
if (response.statusCode == 200) {
final body = json.decode(response.body);

return (body['records'] as List).map((json) {
if (json['fields']['bateau'].toString().toLowerCase() ==
return (body['forecasts'] as List).map((forecast) {
if (forecast['closing_reason'].toString().toLowerCase() ==
'maintenance') {
final maintenanceForecast = MaintenanceForecast.fromJSON(json);

return maintenanceForecast;
return MaintenanceForecast.fromJSON(forecast);
} else if (forecast['closing_reason'].toString().toLowerCase() ==
'special_event') {
return SpecialEventForecast.fromJSON(forecast);
} else {
return BoatForecast.fromJSON(forecast);
}
final boatForecast = BoatForecast.fromJSON(json);

return boatForecast;
}).toList()
..sort((a, b) =>
a.circulationClosingDate.compareTo(b.circulationClosingDate));
}).toList();
}

return [];
}

AbstractForecast? _getCurrentStatus(
List<AbstractForecast> forecast,
) {
int middle = forecast.length ~/ 2;
if ((forecast[middle].circulationClosingDate.isBefore(DateTime.now()) &&
forecast[middle].circulationReOpeningDate.isAfter(DateTime.now()))) {
return forecast[middle];
}
if (forecast.length == 2) {
if (forecast[1].circulationClosingDate.isAfter(DateTime.now()) &&
forecast[0].circulationReOpeningDate.isBefore(DateTime.now())) {
return forecast[1];
Future<AbstractForecast?> _fetchCurrentForecast() async {
var uri =
Uri.https('beta.chabo-api.vareversat.fr', '/v1/forecasts/current');
final response = await httpClient.get(uri, headers: {
'Timezone': 'UTC',
'User-Agent': 'https://github.com/vareversat/chabo-app'
});
if (response.statusCode == 200) {
final body = json.decode(response.body);

var forecast = body['forecast'];

if (forecast['closing_reason'].toString().toLowerCase() ==
'maintenance') {
return MaintenanceForecast.fromJSON(forecast);
} else if (forecast['closing_reason'].toString().toLowerCase() ==
'special_event') {
return SpecialEventForecast.fromJSON(forecast);
} else {
if (!forecast[0].circulationReOpeningDate.isBefore(DateTime.now())) {
return forecast[0];
} else {
return null;
}
return BoatForecast.fromJSON(forecast);
}
} else if (forecast[middle]
.circulationClosingDate
.isAfter(DateTime.now())) {
return _getCurrentStatus(forecast.sublist(0, middle + 1));
} else {
return _getCurrentStatus(forecast.sublist(middle));
}
return null;
}

AbstractForecast? _getPreviousStatus(
List<AbstractForecast> forecasts,
AbstractForecast? currentStatus,
) {
if (currentStatus == null) {
return null;
Future<AbstractForecast?> _fetchNextForecast() async {
var uri = Uri.https('beta.chabo-api.vareversat.fr', '/v1/forecasts/next');
final response = await httpClient.get(uri, headers: {
'Timezone': 'UTC',
'User-Agent': 'https://github.com/vareversat/chabo-app'
});
if (response.statusCode == 200) {
final body = json.decode(response.body);

var forecast = body['forecast'];

if (forecast['closing_reason'].toString().toLowerCase() ==
'maintenance') {
return MaintenanceForecast.fromJSON(forecast);
} else if (forecast['closing_reason'].toString().toLowerCase() ==
'special_event') {
return SpecialEventForecast.fromJSON(forecast);
} else {
return BoatForecast.fromJSON(forecast);
}
}
return forecasts.indexOf(currentStatus) == 0
? null
: forecasts.elementAt(forecasts.indexOf(currentStatus) - 1);
return null;
}

Future<void> _onForecastFetched(
Expand All @@ -131,42 +154,29 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
) async {
if (state.hasReachedMax) return;
try {
if (state.status == ForecastStatus.initial) {
final forecasts = await _fetchForecasts(state.offset);
final currentStatus = _getCurrentStatus(forecasts);
final noMoreForecasts = currentStatus == null;
emit(state.copyWith(
status: ForecastStatus.success,
forecasts: forecasts,
currentForecast: currentStatus,
previousForecast: _getPreviousStatus(forecasts, currentStatus),
noMoreForecasts: noMoreForecasts,
hasReachedMax: false,
offset: state.offset + Const.forecastLimit,
));
}
final forecasts = await _fetchForecasts(state.forecasts.length);
emit(
forecasts.isEmpty
? state.copyWith(hasReachedMax: true)
: state.copyWith(
currentForecast:
state.currentForecast ?? _getCurrentStatus(forecasts),
previousForecast: state.previousForecast ??
_getPreviousStatus(
forecasts,
_getCurrentStatus(forecasts),
),
status: ForecastStatus.success,
forecasts: List.of(state.forecasts)..addAll(forecasts),
hasReachedMax: false,
offset: state.offset + Const.forecastLimit,
),
);
final forecasts = await _fetchForecasts();
final currentForecast = await _fetchCurrentForecast();
final nextForecast = await _fetchNextForecast();
final firstForecast = forecasts[0];

emit(state.copyWith(
bridgeStatus: currentForecast == null
? BridgeStatus.bridgeOpen
: BridgeStatus.bridgeClosed,
forecastStatus: nextForecast == null
? ForecastStatus.forecastsYetToBeAnnounced
: ForecastStatus.forecastsFetched,
forecasts: forecasts,
firstForecast: firstForecast,
currentForecast: currentForecast,
nextForecast: nextForecast,
hasReachedMax: false,
offset: state.offset + Const.forecastLimit,
));
} catch (_) {
emit(state.copyWith(
status: ForecastStatus.failure,
message: _.toString(),
forecastStatus: ForecastStatus.error,
errorMessage: _.toString(),
));
}
}
Expand Down
61 changes: 40 additions & 21 deletions lib/bloc/forecast/forecast_state.dart
Original file line number Diff line number Diff line change
@@ -1,58 +1,77 @@
part of 'forecast_bloc.dart';

class ForecastState extends Equatable {
final ForecastStatus status;
final ForecastStatus forecastStatus;
final BridgeStatus bridgeStatus;
final List<AbstractForecast> forecasts;
final AbstractForecast? firstForecast;
final AbstractForecast? currentForecast;
final AbstractForecast? previousForecast;
final bool noMoreForecasts;
final AbstractForecast? nextForecast;
final bool hasReachedMax;
final int offset;
final String message;
final String errorMessage;

const ForecastState({
this.status = ForecastStatus.initial,
this.forecastStatus = ForecastStatus.toBeFetched,
this.bridgeStatus = BridgeStatus.toBeDetermined,
this.forecasts = const <AbstractForecast>[],
this.firstForecast,
this.currentForecast,
this.previousForecast,
this.hasReachedMax = false,
this.offset = 0,
this.message = 'OK',
this.noMoreForecasts = false,
this.errorMessage = 'OK',
this.nextForecast,
});

ForecastState copyWith({
ForecastStatus? status,
ForecastStatus? forecastStatus,
BridgeStatus? bridgeStatus,
List<AbstractForecast>? forecasts,
AbstractForecast? firstForecast,
AbstractForecast? currentForecast,
AbstractForecast? previousForecast,
AbstractForecast? nextForecast,
bool? noMoreForecasts,
bool? hasReachedMax,
int? offset,
String? message,
String? errorMessage,
}) {
return ForecastState(
status: status ?? this.status,
forecastStatus: forecastStatus ?? this.forecastStatus,
bridgeStatus: bridgeStatus ?? this.bridgeStatus,
forecasts: forecasts ?? this.forecasts,
firstForecast: firstForecast ?? this.firstForecast,
currentForecast: currentForecast ?? this.currentForecast,
previousForecast: previousForecast ?? this.previousForecast,
noMoreForecasts: noMoreForecasts ?? this.noMoreForecasts,
nextForecast: nextForecast ?? this.nextForecast,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
offset: offset ?? this.offset,
message: message ?? this.message,
errorMessage: errorMessage ?? this.errorMessage,
);
}

@override
List<Object?> get props => [
status,
List<Object?> get props =>
[
forecastStatus,
bridgeStatus,
forecasts,
firstForecast,
currentForecast,
nextForecast,
hasReachedMax,
offset,
message,
currentForecast,
previousForecast,
errorMessage,
];
}

enum ForecastStatus { initial, success, failure }
enum ForecastStatus {
toBeFetched,
forecastsFetched,
forecastsYetToBeAnnounced,
error
}

enum BridgeStatus {
toBeDetermined,
bridgeOpen,
bridgeClosed,
}
2 changes: 1 addition & 1 deletion lib/bloc/status/status_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class StatusBloc extends Bloc<StatusEvent, StatusState> {
final previousForecast = state.previousForecast;
if (currentForecast != null && previousForecast != null) {
return currentForecast.isCurrentlyClosed()
? currentForecast.closedDuration
? currentForecast.closingDuration
: currentForecast.circulationClosingDate.difference(
previousForecast.circulationReOpeningDate,
);
Expand Down
4 changes: 4 additions & 0 deletions lib/extensions/color_scheme_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ extension ColorSchemeExtension on ColorScheme {
return brightness == Brightness.light ? Colors.brown : Colors.grey;
}

MaterialColor get specialEventColor {
return brightness == Brightness.light ? Colors.purple : Colors.pink;
}

Color get okColor {
return brightness == Brightness.light
? const Color(0xFF81C784)
Expand Down
Loading

0 comments on commit 2729f02

Please sign in to comment.