migrated the breaker system to MarianumConnect and refactored the API response to a rule-based model with support for version-specific blocks using maxBuild.
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import '../../errors/marianumconnect_error.dart';
|
||||||
|
import '../../marianumconnect_api.dart';
|
||||||
|
import '../../marianumconnect_endpoint.dart';
|
||||||
|
import 'get_breakers_response.dart';
|
||||||
|
|
||||||
|
/// Fetches the app maintenance breaker rules from `GET /api/mobile/v1/breaker`.
|
||||||
|
/// The endpoint is public: the bearer token is attached if present but not
|
||||||
|
/// required, so this also works before login (e.g. to block the whole app).
|
||||||
|
class GetBreakers {
|
||||||
|
final Dio _dio;
|
||||||
|
|
||||||
|
GetBreakers({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||||
|
|
||||||
|
Future<GetBreakersResponse> run() async {
|
||||||
|
try {
|
||||||
|
final response = await _dio.get<Map<String, dynamic>>(
|
||||||
|
MarianumConnectEndpoint.resolve('breaker'),
|
||||||
|
);
|
||||||
|
return GetBreakersResponse.fromJson(response.data!);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
throw mapMarianumConnectError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import 'get_breakers.dart';
|
|||||||
import 'get_breakers_response.dart';
|
import 'get_breakers_response.dart';
|
||||||
|
|
||||||
class GetBreakersCache extends SimpleCache<GetBreakersResponse> {
|
class GetBreakersCache extends SimpleCache<GetBreakersResponse> {
|
||||||
GetBreakersCache({super.onUpdate, super.renew})
|
GetBreakersCache({super.onUpdate, super.renew, super.onError})
|
||||||
: super(
|
: super(
|
||||||
cacheTime: RequestCache.cacheMinute,
|
cacheTime: RequestCache.cacheMinute,
|
||||||
loader: () => GetBreakers().run(),
|
loader: () => GetBreakers().run(),
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
import '../../../api_response.dart';
|
||||||
|
|
||||||
|
part 'get_breakers_response.g.dart';
|
||||||
|
|
||||||
|
/// App maintenance breaker rules delivered by
|
||||||
|
/// `GET /api/mobile/v1/breaker`. Each rule blocks the listed [areas] with a
|
||||||
|
/// [message]; [BreakerRule.maxBuild] optionally scopes it to old app builds.
|
||||||
|
///
|
||||||
|
/// [rules] defaults to empty so a payload in the pre-migration shape
|
||||||
|
/// (`global`/`regional`) — which may still sit in the hydrated/cache store
|
||||||
|
/// after an update — parses to "nothing blocked" instead of throwing.
|
||||||
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
class GetBreakersResponse extends ApiResponse {
|
||||||
|
@JsonKey(defaultValue: [])
|
||||||
|
List<BreakerRule> rules;
|
||||||
|
|
||||||
|
GetBreakersResponse(this.rules);
|
||||||
|
|
||||||
|
factory GetBreakersResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$GetBreakersResponseFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$GetBreakersResponseToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class BreakerRule {
|
||||||
|
List<BreakerArea> areas;
|
||||||
|
String message;
|
||||||
|
|
||||||
|
/// When set, the rule only applies to app builds whose build number is
|
||||||
|
/// ≤ [maxBuild] (force-update of old versions). `null` = every build.
|
||||||
|
int? maxBuild;
|
||||||
|
|
||||||
|
BreakerRule(this.areas, this.message, this.maxBuild);
|
||||||
|
|
||||||
|
factory BreakerRule.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$BreakerRuleFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$BreakerRuleToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blockable areas of the app. Each app module maps itself to one of these via
|
||||||
|
/// its `breakerArea` (see `app_modules.dart`). Must stay in sync with the
|
||||||
|
/// backend enum `BreakerArea` in the MarianumConnect `marMobileApi` service —
|
||||||
|
/// the backend only offers these values in the admin UI, so when a new blockable
|
||||||
|
/// module is added here, add the matching value on the backend too.
|
||||||
|
enum BreakerArea {
|
||||||
|
@JsonValue('GLOBAL')
|
||||||
|
global,
|
||||||
|
@JsonValue('TIMETABLE')
|
||||||
|
timetable,
|
||||||
|
@JsonValue('TALK')
|
||||||
|
talk,
|
||||||
|
@JsonValue('FILES')
|
||||||
|
files,
|
||||||
|
@JsonValue('NEWS')
|
||||||
|
news,
|
||||||
|
@JsonValue('ROOMPLAN')
|
||||||
|
roomPlan,
|
||||||
|
@JsonValue('GRADES')
|
||||||
|
grades,
|
||||||
|
@JsonValue('HOLIDAYS')
|
||||||
|
holidays,
|
||||||
|
@JsonValue('DATES')
|
||||||
|
dates,
|
||||||
|
@JsonValue('FEEDBACK')
|
||||||
|
feedback,
|
||||||
|
@JsonValue('MORE')
|
||||||
|
more,
|
||||||
|
}
|
||||||
+19
-20
@@ -8,15 +8,10 @@ part of 'get_breakers_response.dart';
|
|||||||
|
|
||||||
GetBreakersResponse _$GetBreakersResponseFromJson(Map<String, dynamic> json) =>
|
GetBreakersResponse _$GetBreakersResponseFromJson(Map<String, dynamic> json) =>
|
||||||
GetBreakersResponse(
|
GetBreakersResponse(
|
||||||
GetBreakersReponseObject.fromJson(
|
(json['rules'] as List<dynamic>?)
|
||||||
json['global'] as Map<String, dynamic>,
|
?.map((e) => BreakerRule.fromJson(e as Map<String, dynamic>))
|
||||||
),
|
.toList() ??
|
||||||
(json['regional'] as Map<String, dynamic>).map(
|
[],
|
||||||
(k, e) => MapEntry(
|
|
||||||
k,
|
|
||||||
GetBreakersReponseObject.fromJson(e as Map<String, dynamic>),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, e as String),
|
(k, e) => MapEntry(k, e as String),
|
||||||
@@ -26,30 +21,34 @@ Map<String, dynamic> _$GetBreakersResponseToJson(
|
|||||||
GetBreakersResponse instance,
|
GetBreakersResponse instance,
|
||||||
) => <String, dynamic>{
|
) => <String, dynamic>{
|
||||||
'headers': ?instance.headers,
|
'headers': ?instance.headers,
|
||||||
'global': instance.global.toJson(),
|
'rules': instance.rules.map((e) => e.toJson()).toList(),
|
||||||
'regional': instance.regional.map((k, e) => MapEntry(k, e.toJson())),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GetBreakersReponseObject _$GetBreakersReponseObjectFromJson(
|
BreakerRule _$BreakerRuleFromJson(Map<String, dynamic> json) => BreakerRule(
|
||||||
Map<String, dynamic> json,
|
|
||||||
) => GetBreakersReponseObject(
|
|
||||||
(json['areas'] as List<dynamic>)
|
(json['areas'] as List<dynamic>)
|
||||||
.map((e) => $enumDecode(_$BreakerAreaEnumMap, e))
|
.map((e) => $enumDecode(_$BreakerAreaEnumMap, e))
|
||||||
.toList(),
|
.toList(),
|
||||||
json['message'] as String,
|
json['message'] as String,
|
||||||
|
(json['maxBuild'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$GetBreakersReponseObjectToJson(
|
Map<String, dynamic> _$BreakerRuleToJson(BreakerRule instance) =>
|
||||||
GetBreakersReponseObject instance,
|
<String, dynamic>{
|
||||||
) => <String, dynamic>{
|
'areas': instance.areas.map((e) => _$BreakerAreaEnumMap[e]!).toList(),
|
||||||
'areas': instance.areas.map((e) => _$BreakerAreaEnumMap[e]!).toList(),
|
'message': instance.message,
|
||||||
'message': instance.message,
|
'maxBuild': instance.maxBuild,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$BreakerAreaEnumMap = {
|
const _$BreakerAreaEnumMap = {
|
||||||
BreakerArea.global: 'GLOBAL',
|
BreakerArea.global: 'GLOBAL',
|
||||||
BreakerArea.timetable: 'TIMETABLE',
|
BreakerArea.timetable: 'TIMETABLE',
|
||||||
BreakerArea.talk: 'TALK',
|
BreakerArea.talk: 'TALK',
|
||||||
BreakerArea.files: 'FILES',
|
BreakerArea.files: 'FILES',
|
||||||
|
BreakerArea.news: 'NEWS',
|
||||||
|
BreakerArea.roomPlan: 'ROOMPLAN',
|
||||||
|
BreakerArea.grades: 'GRADES',
|
||||||
|
BreakerArea.holidays: 'HOLIDAYS',
|
||||||
|
BreakerArea.dates: 'DATES',
|
||||||
|
BreakerArea.feedback: 'FEEDBACK',
|
||||||
BreakerArea.more: 'MORE',
|
BreakerArea.more: 'MORE',
|
||||||
};
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
|
|
||||||
import '../../mhsl_api.dart';
|
|
||||||
import 'get_breakers_response.dart';
|
|
||||||
|
|
||||||
class GetBreakers extends MhslApi<GetBreakersResponse> {
|
|
||||||
GetBreakers() : super('breaker/');
|
|
||||||
|
|
||||||
@override
|
|
||||||
GetBreakersResponse assemble(String raw) =>
|
|
||||||
GetBreakersResponse.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Response>? request(Uri uri) => http.get(uri);
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
import '../../../api_response.dart';
|
|
||||||
|
|
||||||
part 'get_breakers_response.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
|
||||||
class GetBreakersResponse extends ApiResponse {
|
|
||||||
GetBreakersReponseObject global;
|
|
||||||
Map<String, GetBreakersReponseObject> regional;
|
|
||||||
|
|
||||||
GetBreakersResponse(this.global, this.regional);
|
|
||||||
|
|
||||||
factory GetBreakersResponse.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$GetBreakersResponseFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$GetBreakersResponseToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class GetBreakersReponseObject {
|
|
||||||
List<BreakerArea> areas;
|
|
||||||
String message;
|
|
||||||
|
|
||||||
GetBreakersReponseObject(this.areas, this.message);
|
|
||||||
|
|
||||||
factory GetBreakersReponseObject.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$GetBreakersReponseObjectFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$GetBreakersReponseObjectToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BreakerArea {
|
|
||||||
@JsonValue('GLOBAL')
|
|
||||||
global,
|
|
||||||
@JsonValue('TIMETABLE')
|
|
||||||
timetable,
|
|
||||||
@JsonValue('TALK')
|
|
||||||
talk,
|
|
||||||
@JsonValue('FILES')
|
|
||||||
files,
|
|
||||||
@JsonValue('MORE')
|
|
||||||
more,
|
|
||||||
}
|
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
import 'api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import 'api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import 'api/mhsl/server/user_index/update/update_userindex.dart';
|
import 'api/mhsl/server/user_index/update/update_userindex.dart';
|
||||||
import 'main.dart';
|
import 'main.dart';
|
||||||
import 'model/data_cleaner.dart';
|
import 'model/data_cleaner.dart';
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'api/marianumcloud/webdav/queries/list_files/list_files_cache.dart';
|
import 'api/marianumcloud/webdav/queries/list_files/list_files_cache.dart';
|
||||||
import 'api/marianumconnect/auth/session_validator.dart';
|
import 'api/marianumconnect/auth/session_validator.dart';
|
||||||
import 'api/marianumconnect/marianumconnect_endpoint.dart';
|
import 'api/marianumconnect/marianumconnect_endpoint.dart';
|
||||||
import 'api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import 'api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'background/widget_background_task.dart';
|
import 'background/widget_background_task.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
import '../../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import '../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import '../../../routing/app_routes.dart';
|
import '../../../routing/app_routes.dart';
|
||||||
import '../../../view/pages/files/files.dart';
|
import '../../../view/pages/files/files.dart';
|
||||||
import '../../../view/pages/grade_averages/grade_averages_view.dart';
|
import '../../../view/pages/grade_averages/grade_averages_view.dart';
|
||||||
@@ -95,35 +95,35 @@ class AppModule {
|
|||||||
Modules.marianumMessage,
|
Modules.marianumMessage,
|
||||||
name: 'Marianum Message',
|
name: 'Marianum Message',
|
||||||
icon: () => Icon(Icons.newspaper),
|
icon: () => Icon(Icons.newspaper),
|
||||||
breakerArea: BreakerArea.more,
|
breakerArea: BreakerArea.news,
|
||||||
create: MarianumMessageListView.new,
|
create: MarianumMessageListView.new,
|
||||||
),
|
),
|
||||||
Modules.roomPlan: AppModule(
|
Modules.roomPlan: AppModule(
|
||||||
Modules.roomPlan,
|
Modules.roomPlan,
|
||||||
name: 'Raumplan',
|
name: 'Raumplan',
|
||||||
icon: () => Icon(Icons.location_pin),
|
icon: () => Icon(Icons.location_pin),
|
||||||
breakerArea: BreakerArea.more,
|
breakerArea: BreakerArea.roomPlan,
|
||||||
create: Roomplan.new,
|
create: Roomplan.new,
|
||||||
),
|
),
|
||||||
Modules.gradeAveragesCalculator: AppModule(
|
Modules.gradeAveragesCalculator: AppModule(
|
||||||
Modules.gradeAveragesCalculator,
|
Modules.gradeAveragesCalculator,
|
||||||
name: 'Notendurchschnittsrechner',
|
name: 'Notendurchschnittsrechner',
|
||||||
icon: () => Icon(Icons.calculate),
|
icon: () => Icon(Icons.calculate),
|
||||||
breakerArea: BreakerArea.more,
|
breakerArea: BreakerArea.grades,
|
||||||
create: GradeAveragesView.new,
|
create: GradeAveragesView.new,
|
||||||
),
|
),
|
||||||
Modules.holidays: AppModule(
|
Modules.holidays: AppModule(
|
||||||
Modules.holidays,
|
Modules.holidays,
|
||||||
name: 'Schulferien',
|
name: 'Schulferien',
|
||||||
icon: () => Icon(Icons.beach_access_outlined),
|
icon: () => Icon(Icons.beach_access_outlined),
|
||||||
breakerArea: BreakerArea.more,
|
breakerArea: BreakerArea.holidays,
|
||||||
create: HolidaysView.new,
|
create: HolidaysView.new,
|
||||||
),
|
),
|
||||||
Modules.marianumDates: AppModule(
|
Modules.marianumDates: AppModule(
|
||||||
Modules.marianumDates,
|
Modules.marianumDates,
|
||||||
name: 'Marianum Termine',
|
name: 'Marianum Termine',
|
||||||
icon: () => Icon(Icons.event_note),
|
icon: () => Icon(Icons.event_note),
|
||||||
breakerArea: BreakerArea.more,
|
breakerArea: BreakerArea.dates,
|
||||||
create: MarianumDatesView.new,
|
create: MarianumDatesView.new,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import '../../../../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import '../../../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
|
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
|
||||||
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
|
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
|
||||||
import '../repository/breaker_repository.dart';
|
import '../repository/breaker_repository.dart';
|
||||||
@@ -40,13 +40,11 @@ class BreakerBloc
|
|||||||
final response = innerState?.response;
|
final response = innerState?.response;
|
||||||
if (response == null || _packageInfo == null) return null;
|
if (response == null || _packageInfo == null) return null;
|
||||||
|
|
||||||
if (response.global.areas.contains(type)) return response.global.message;
|
final selfBuild = int.tryParse(_packageInfo!.buildNumber) ?? 0;
|
||||||
|
for (final rule in response.rules) {
|
||||||
final selfBuild = int.parse(_packageInfo!.buildNumber);
|
if (!rule.areas.contains(type)) continue;
|
||||||
for (final entry in response.regional.entries) {
|
if (rule.maxBuild == null || selfBuild <= rule.maxBuild!) {
|
||||||
final affectedBuild = int.parse(entry.key.split('b')[1]);
|
return rule.message;
|
||||||
if (affectedBuild >= selfBuild && entry.value.areas.contains(type)) {
|
|
||||||
return entry.value.message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import '../../../../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import '../../../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
|
|
||||||
part 'breaker_state.freezed.dart';
|
part 'breaker_state.freezed.dart';
|
||||||
part 'breaker_state.g.dart';
|
part 'breaker_state.g.dart';
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import 'dart:async';
|
import '../../../../../api/marianumconnect/queries/get_breakers/get_breakers_cache.dart';
|
||||||
|
import '../../../../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import '../../../../../api/mhsl/breaker/get_breakers/get_breakers_cache.dart';
|
import '../../../../../api/request_cache.dart';
|
||||||
import '../../../../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
|
||||||
|
|
||||||
class BreakerDataProvider {
|
class BreakerDataProvider {
|
||||||
Future<GetBreakersResponse> getBreakers() {
|
/// Resolves via [resolveFromCache] (not first-emit-wins): the cache emits its
|
||||||
final completer = Completer<GetBreakersResponse>();
|
/// stored value first for an instant display, then the network value — and we
|
||||||
GetBreakersCache(
|
/// return the latter once [RequestCache.ready] settles. `renew: true` forces a
|
||||||
onUpdate: (data) {
|
/// fresh network check on every call so a just-changed breaker takes effect on
|
||||||
if (!completer.isCompleted) completer.complete(data);
|
/// the next `refresh()` (app resume) instead of only after a restart.
|
||||||
},
|
Future<GetBreakersResponse> getBreakers({
|
||||||
);
|
bool renew = true,
|
||||||
return completer.future;
|
void Function(Object)? onError,
|
||||||
}
|
}) => resolveFromCache<GetBreakersResponse>(
|
||||||
|
(onUpdate, onError) =>
|
||||||
|
GetBreakersCache(renew: renew, onUpdate: onUpdate, onError: onError),
|
||||||
|
onError: onError,
|
||||||
|
operationName: 'getBreakers',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
import '../../api/marianumconnect/queries/get_breakers/get_breakers_response.dart';
|
||||||
import '../../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
import '../../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
||||||
import '../../widget/placeholder_view.dart';
|
|
||||||
|
|
||||||
class Breaker extends StatelessWidget {
|
class Breaker extends StatelessWidget {
|
||||||
final BreakerArea breaker;
|
final BreakerArea breaker;
|
||||||
@@ -16,13 +15,71 @@ class Breaker extends StatelessWidget {
|
|||||||
final bloc = context.watch<BreakerBloc>();
|
final bloc = context.watch<BreakerBloc>();
|
||||||
final blocked = bloc.isBlocked(breaker);
|
final blocked = bloc.isBlocked(breaker);
|
||||||
if (blocked != null) {
|
if (blocked != null) {
|
||||||
return PlaceholderView(
|
return _BreakerBlockedView(message: blocked);
|
||||||
icon: Icons.app_blocking_outlined,
|
|
||||||
text:
|
|
||||||
'Die App / Dieser Bereich ist zurzeit nicht verfügbar!\n\n'
|
|
||||||
'${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt.\nAktualisiere die App und versuche es später erneut" : blocked}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BreakerBlockedView extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const _BreakerBlockedView({required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scheme = theme.colorScheme;
|
||||||
|
final reason = message.trim();
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: scheme.surface,
|
||||||
|
child: CustomScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 24,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.construction_outlined,
|
||||||
|
size: 60,
|
||||||
|
color: scheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
'Momentan nicht verfügbar',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: scheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (reason.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
reason,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: scheme.onSurfaceVariant,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user