wip: bloc for holidays

This commit is contained in:
Elias Müller 2024-05-14 14:54:01 +02:00
parent 328c4f410c
commit 634fe41e78
23 changed files with 803 additions and 40 deletions

View File

@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'getHolidaysResponse.dart'; import 'getHolidaysResponse.dart';
@ -7,11 +6,10 @@ import 'getHolidaysResponse.dart';
class GetHolidays { class GetHolidays {
Future<GetHolidaysResponse> query() async { Future<GetHolidaysResponse> query() async {
var response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body; var response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body;
var data = jsonDecode(response) as List<dynamic>;
return GetHolidaysResponse( return GetHolidaysResponse(
List<GetHolidaysResponseObject>.from( List<GetHolidaysResponseObject>.from(
jsonDecode(response).map<GetHolidaysResponseObject>( data.map<GetHolidaysResponseObject>((e) => GetHolidaysResponseObject.fromJson(e as Map<String, dynamic>))
GetHolidaysResponseObject.fromJson
)
) )
); );
} }

View File

@ -13,12 +13,11 @@ class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
GetHolidaysResponse onLocalData(String json) { GetHolidaysResponse onLocalData(String json) {
List<dynamic> parsedListJson = jsonDecode(json)['data']; List<dynamic> parsedListJson = jsonDecode(json)['data'];
return GetHolidaysResponse( return GetHolidaysResponse(
List<GetHolidaysResponseObject>.from( List<GetHolidaysResponseObject>.from(
parsedListJson.map<GetHolidaysResponseObject>( parsedListJson.map<GetHolidaysResponseObject>(
// ignore: unnecessary_lambdas (i) => GetHolidaysResponseObject.fromJson(i as Map<String, dynamic>)
(dynamic i) => GetHolidaysResponseObject.fromJson(i)
)
) )
)
); );
} }

View File

@ -0,0 +1,9 @@
import 'package:dio/dio.dart';
import '../../infrastructure/dataLoader/data_loader.dart';
abstract class HolidayDataLoader<TResult> extends DataLoader<TResult> {
HolidayDataLoader() : super(Dio(BaseOptions(
baseUrl: 'https://ferien-api.de/api/v1/',
)));
}

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'data_loader.dart'; import '../../infrastructure/dataLoader/data_loader.dart';
abstract class MhslDataLoader<TResult> extends DataLoader<TResult> { abstract class MhslDataLoader<TResult> extends DataLoader<TResult> {
MhslDataLoader() : super(Dio(BaseOptions( MhslDataLoader() : super(Dio(BaseOptions(

View File

@ -35,8 +35,12 @@ abstract class DataLoader<TResult> {
} }
class DataLoaderResult { class DataLoaderResult {
final Map<String, dynamic> json; final dynamic json;
final Map<String, String> headers; final Map<String, String> headers;
Map<String, dynamic> asMap() => json as Map<String, dynamic>;
List<dynamic> asList() => json as List<dynamic>;
List<Map<String, dynamic>> asListOfMaps() => asList().map((e) => e as Map<String, dynamic>).toList();
DataLoaderResult({required this.json, required this.headers}); DataLoaderResult({required this.json, required this.headers});
} }

View File

@ -9,11 +9,11 @@ class LoadableState<TState> with _$LoadableState {
const LoadableState._(); const LoadableState._();
const factory LoadableState({ const factory LoadableState({
@Default(true) bool isLoading, required bool isLoading,
@Default(null) TState? data, required TState? data,
@Default(null) int? lastFetch, required int? lastFetch,
@Default(null) void Function()? reFetch, required void Function()? reFetch,
@Default(null) LoadingError? error, required LoadingError? error,
}) = _LoadableState; }) = _LoadableState;
bool _hasError() => error != null; bool _hasError() => error != null;

View File

@ -89,4 +89,3 @@ class _LoadableStateErrorBarTextState extends State<LoadableStateErrorBarText> {
super.dispose(); super.dispose();
} }
} }

View File

@ -17,23 +17,40 @@ abstract class LoadableHydratedBloc<
LoadableState<TState> LoadableState<TState>
> { > {
late TRepository _repository; late TRepository _repository;
LoadableHydratedBloc() : super(const LoadableState()) { LoadableHydratedBloc() : super(const LoadableState(
error: null,
data: null,
isLoading: true,
lastFetch: null,
reFetch: null,
)) {
on<Emit<TState>>((event, emit) => emit(LoadableState( on<Emit<TState>>((event, emit) {
isLoading: event.loading, emit(LoadableState(
isLoading: state.isLoading,
data: event.state(innerState ?? fromNothing()), data: event.state(innerState ?? fromNothing()),
lastFetch: DateTime.now().millisecondsSinceEpoch, lastFetch: state.lastFetch,
reFetch: retry reFetch: retry,
error: state.error,
));
});
on<DataGathered<TState>>((event, emit) => emit(LoadableState(
isLoading: false,
data: event.state(innerState ?? fromNothing()),
lastFetch: DateTime.now().millisecondsSinceEpoch,
reFetch: retry,
error: null,
))); )));
on<RefetchStarted<TState>>((event, emit) => emit(LoadableState( on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(
isLoading: true, isLoading: true,
data: innerState, data: innerState,
lastFetch: state.lastFetch lastFetch: state.lastFetch,
reFetch: null,
error: null,
))); )));
on<ClearState<TState>>((event, emit) => emit(const LoadableState()));
on<Error<TState>>((event, emit) => emit(LoadableState( on<Error<TState>>((event, emit) => emit(LoadableState(
isLoading: false, isLoading: false,
data: innerState, data: innerState,
@ -61,7 +78,7 @@ abstract class LoadableHydratedBloc<
(e) { (e) {
log('Error while fetching ${TState.toString()}: ${e.toString()}'); log('Error while fetching ${TState.toString()}: ${e.toString()}');
add(Error(LoadingError( add(Error(LoadingError(
message: e.message, message: e.message ?? e.toString(),
allowRetry: true, allowRetry: true,
))); )));
}, },
@ -73,14 +90,30 @@ abstract class LoadableHydratedBloc<
@override @override
fromJson(Map<String, dynamic> json) { fromJson(Map<String, dynamic> json) {
var rawData = LoadableSaveContext.unwrap(json); var rawData = LoadableSaveContext.unwrap(json);
return LoadableState(isLoading: true, lastFetch: rawData.meta.timestamp, data: fromStorage(rawData.data)); return LoadableState(
isLoading: true,
data: fromStorage(rawData.data),
lastFetch: rawData.meta.timestamp,
reFetch: null,
error: null,
);
} }
@override @override
Map<String, dynamic>? toJson(LoadableState<TState> state) => LoadableSaveContext.wrap( Map<String, dynamic>? toJson(LoadableState<TState> state) {
toStorage(state.data), Map<String, dynamic>? data;
try {
data = toStorage(state.data);
} catch(e) {
log('Failed to save state ${TState.toString()}: ${e.toString()}');
data = null;
}
return LoadableSaveContext.wrap(
data,
state.lastFetch ?? DateTime.now().millisecondsSinceEpoch state.lastFetch ?? DateTime.now().millisecondsSinceEpoch
); );
}
Future<void> gatherData(); Future<void> gatherData();
TRepository repository(); TRepository repository();

View File

@ -3,10 +3,12 @@ import '../../loadableState/loading_error.dart';
class LoadableHydratedBlocEvent<TState> {} class LoadableHydratedBlocEvent<TState> {}
class Emit<TState> extends LoadableHydratedBlocEvent<TState> { class Emit<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state; final TState Function(TState state) state;
final bool loading; Emit(this.state);
Emit(this.state, {this.loading = false}); }
class DataGathered<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state;
DataGathered(this.state);
} }
class ClearState<TState> extends LoadableHydratedBlocEvent<TState> {}
class Error<TState> extends LoadableHydratedBlocEvent<TState> { class Error<TState> extends LoadableHydratedBlocEvent<TState> {
final LoadingError error; final LoadingError error;
Error(this.error); Error(this.error);

View File

@ -21,4 +21,3 @@ class LoadableSaveContext with _$LoadableSaveContext {
static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) => static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) =>
(data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey])); (data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey]));
} }

View File

@ -4,12 +4,12 @@ import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
import '../../../model/breakers/Breaker.dart'; import '../../../model/breakers/Breaker.dart';
import '../../../view/pages/files/files.dart'; import '../../../view/pages/files/files.dart';
import '../../../view/pages/more/holidays/holidays.dart';
import '../../../view/pages/more/roomplan/roomplan.dart'; import '../../../view/pages/more/roomplan/roomplan.dart';
import '../../../view/pages/talk/chatList.dart'; import '../../../view/pages/talk/chatList.dart';
import '../../../view/pages/timetable/timetable.dart'; import '../../../view/pages/timetable/timetable.dart';
import '../../../widget/centeredLeading.dart'; import '../../../widget/centeredLeading.dart';
import 'gradeAverages/view/grade_averages_view.dart'; import 'gradeAverages/view/grade_averages_view.dart';
import 'holidays/view/holidays_view.dart';
import 'marianumMessage/view/marianum_message_list_view.dart'; import 'marianumMessage/view/marianum_message_list_view.dart';
class AppModule { class AppModule {
@ -26,7 +26,7 @@ class AppModule {
Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new),
Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new),
Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new),
Modules.holidays: AppModule('Schulferien', Icons.holiday_village, Holidays.new), Modules.holidays: AppModule('Schulferien', Icons.holiday_village, HolidaysView.new),
}; };
static AppModule getModule(Modules module) => modules()[module]!; static AppModule getModule(Modules module) => modules()[module]!;

View File

@ -0,0 +1,36 @@
import 'dart:developer';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import '../repository/holidays_repository.dart';
import 'holidays_event.dart';
import 'holidays_state.dart';
class HolidaysBloc extends LoadableHydratedBloc<HolidaysEvent, HolidaysState, HolidaysRepository> {
HolidaysBloc() {
on<SetPastHolidaysVisible>((event, emit) {
log('set pastholidays: ${event.shouldBeVisible.toString()}');
add(Emit((state) => state.copyWith(showPastHolidays: state.showPastHolidays)));
});
on<DisclaimerDismissed>((event, emit) => add(
Emit((state) => state.copyWith(showDisclaimer: false))
));
}
bool showPastHolidays() => innerState?.showPastHolidays ?? false;
@override
fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true);
@override
fromStorage(Map<String, dynamic> json) => HolidaysState.fromJson(json);
@override
Future<void> gatherData() async {
var holidays = await repo.getHolidays();
add(DataGathered((state) => state.copyWith(holidays: holidays)));
}
@override
repository() => HolidaysRepository();
@override
Map<String, dynamic>? toStorage(state) => state.toJson();
}

View File

@ -0,0 +1,9 @@
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import 'holidays_state.dart';
sealed class HolidaysEvent extends LoadableHydratedBlocEvent<HolidaysState> {}
class SetPastHolidaysVisible extends HolidaysEvent {
final bool shouldBeVisible;
SetPastHolidaysVisible(this.shouldBeVisible);
}
class DisclaimerDismissed extends HolidaysEvent {}

View File

@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'holidays_state.freezed.dart';
part 'holidays_state.g.dart';
@freezed
class HolidaysState with _$HolidaysState {
const factory HolidaysState({
required bool showPastHolidays,
required bool showDisclaimer,
required List<Holiday> holidays,
}) = _HolidaysState;
factory HolidaysState.fromJson(Map<String, Object?> json) => _$HolidaysStateFromJson(json);
}
@freezed
class Holiday with _$Holiday {
const factory Holiday({
required String start,
required String end,
required int year,
required String stateCode,
required String name,
required String slug,
}) = _Holiday;
factory Holiday.fromJson(Map<String, Object?> json) => _$HolidayFromJson(json);
}

View File

@ -0,0 +1,463 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'holidays_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
HolidaysState _$HolidaysStateFromJson(Map<String, dynamic> json) {
return _HolidaysState.fromJson(json);
}
/// @nodoc
mixin _$HolidaysState {
bool get showPastHolidays => throw _privateConstructorUsedError;
bool get showDisclaimer => throw _privateConstructorUsedError;
List<Holiday> get holidays => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HolidaysStateCopyWith<HolidaysState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HolidaysStateCopyWith<$Res> {
factory $HolidaysStateCopyWith(
HolidaysState value, $Res Function(HolidaysState) then) =
_$HolidaysStateCopyWithImpl<$Res, HolidaysState>;
@useResult
$Res call(
{bool showPastHolidays, bool showDisclaimer, List<Holiday> holidays});
}
/// @nodoc
class _$HolidaysStateCopyWithImpl<$Res, $Val extends HolidaysState>
implements $HolidaysStateCopyWith<$Res> {
_$HolidaysStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? showPastHolidays = null,
Object? showDisclaimer = null,
Object? holidays = null,
}) {
return _then(_value.copyWith(
showPastHolidays: null == showPastHolidays
? _value.showPastHolidays
: showPastHolidays // ignore: cast_nullable_to_non_nullable
as bool,
showDisclaimer: null == showDisclaimer
? _value.showDisclaimer
: showDisclaimer // ignore: cast_nullable_to_non_nullable
as bool,
holidays: null == holidays
? _value.holidays
: holidays // ignore: cast_nullable_to_non_nullable
as List<Holiday>,
) as $Val);
}
}
/// @nodoc
abstract class _$$HolidaysStateImplCopyWith<$Res>
implements $HolidaysStateCopyWith<$Res> {
factory _$$HolidaysStateImplCopyWith(
_$HolidaysStateImpl value, $Res Function(_$HolidaysStateImpl) then) =
__$$HolidaysStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool showPastHolidays, bool showDisclaimer, List<Holiday> holidays});
}
/// @nodoc
class __$$HolidaysStateImplCopyWithImpl<$Res>
extends _$HolidaysStateCopyWithImpl<$Res, _$HolidaysStateImpl>
implements _$$HolidaysStateImplCopyWith<$Res> {
__$$HolidaysStateImplCopyWithImpl(
_$HolidaysStateImpl _value, $Res Function(_$HolidaysStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? showPastHolidays = null,
Object? showDisclaimer = null,
Object? holidays = null,
}) {
return _then(_$HolidaysStateImpl(
showPastHolidays: null == showPastHolidays
? _value.showPastHolidays
: showPastHolidays // ignore: cast_nullable_to_non_nullable
as bool,
showDisclaimer: null == showDisclaimer
? _value.showDisclaimer
: showDisclaimer // ignore: cast_nullable_to_non_nullable
as bool,
holidays: null == holidays
? _value._holidays
: holidays // ignore: cast_nullable_to_non_nullable
as List<Holiday>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HolidaysStateImpl
with DiagnosticableTreeMixin
implements _HolidaysState {
const _$HolidaysStateImpl(
{required this.showPastHolidays,
required this.showDisclaimer,
required final List<Holiday> holidays})
: _holidays = holidays;
factory _$HolidaysStateImpl.fromJson(Map<String, dynamic> json) =>
_$$HolidaysStateImplFromJson(json);
@override
final bool showPastHolidays;
@override
final bool showDisclaimer;
final List<Holiday> _holidays;
@override
List<Holiday> get holidays {
if (_holidays is EqualUnmodifiableListView) return _holidays;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_holidays);
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'HolidaysState(showPastHolidays: $showPastHolidays, showDisclaimer: $showDisclaimer, holidays: $holidays)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'HolidaysState'))
..add(DiagnosticsProperty('showPastHolidays', showPastHolidays))
..add(DiagnosticsProperty('showDisclaimer', showDisclaimer))
..add(DiagnosticsProperty('holidays', holidays));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HolidaysStateImpl &&
(identical(other.showPastHolidays, showPastHolidays) ||
other.showPastHolidays == showPastHolidays) &&
(identical(other.showDisclaimer, showDisclaimer) ||
other.showDisclaimer == showDisclaimer) &&
const DeepCollectionEquality().equals(other._holidays, _holidays));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, showPastHolidays, showDisclaimer,
const DeepCollectionEquality().hash(_holidays));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HolidaysStateImplCopyWith<_$HolidaysStateImpl> get copyWith =>
__$$HolidaysStateImplCopyWithImpl<_$HolidaysStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HolidaysStateImplToJson(
this,
);
}
}
abstract class _HolidaysState implements HolidaysState {
const factory _HolidaysState(
{required final bool showPastHolidays,
required final bool showDisclaimer,
required final List<Holiday> holidays}) = _$HolidaysStateImpl;
factory _HolidaysState.fromJson(Map<String, dynamic> json) =
_$HolidaysStateImpl.fromJson;
@override
bool get showPastHolidays;
@override
bool get showDisclaimer;
@override
List<Holiday> get holidays;
@override
@JsonKey(ignore: true)
_$$HolidaysStateImplCopyWith<_$HolidaysStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
Holiday _$HolidayFromJson(Map<String, dynamic> json) {
return _Holiday.fromJson(json);
}
/// @nodoc
mixin _$Holiday {
String get start => throw _privateConstructorUsedError;
String get end => throw _privateConstructorUsedError;
int get year => throw _privateConstructorUsedError;
String get stateCode => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get slug => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$HolidayCopyWith<Holiday> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HolidayCopyWith<$Res> {
factory $HolidayCopyWith(Holiday value, $Res Function(Holiday) then) =
_$HolidayCopyWithImpl<$Res, Holiday>;
@useResult
$Res call(
{String start,
String end,
int year,
String stateCode,
String name,
String slug});
}
/// @nodoc
class _$HolidayCopyWithImpl<$Res, $Val extends Holiday>
implements $HolidayCopyWith<$Res> {
_$HolidayCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? start = null,
Object? end = null,
Object? year = null,
Object? stateCode = null,
Object? name = null,
Object? slug = null,
}) {
return _then(_value.copyWith(
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as String,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as String,
year: null == year
? _value.year
: year // ignore: cast_nullable_to_non_nullable
as int,
stateCode: null == stateCode
? _value.stateCode
: stateCode // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$HolidayImplCopyWith<$Res> implements $HolidayCopyWith<$Res> {
factory _$$HolidayImplCopyWith(
_$HolidayImpl value, $Res Function(_$HolidayImpl) then) =
__$$HolidayImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String start,
String end,
int year,
String stateCode,
String name,
String slug});
}
/// @nodoc
class __$$HolidayImplCopyWithImpl<$Res>
extends _$HolidayCopyWithImpl<$Res, _$HolidayImpl>
implements _$$HolidayImplCopyWith<$Res> {
__$$HolidayImplCopyWithImpl(
_$HolidayImpl _value, $Res Function(_$HolidayImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? start = null,
Object? end = null,
Object? year = null,
Object? stateCode = null,
Object? name = null,
Object? slug = null,
}) {
return _then(_$HolidayImpl(
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as String,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as String,
year: null == year
? _value.year
: year // ignore: cast_nullable_to_non_nullable
as int,
stateCode: null == stateCode
? _value.stateCode
: stateCode // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HolidayImpl with DiagnosticableTreeMixin implements _Holiday {
const _$HolidayImpl(
{required this.start,
required this.end,
required this.year,
required this.stateCode,
required this.name,
required this.slug});
factory _$HolidayImpl.fromJson(Map<String, dynamic> json) =>
_$$HolidayImplFromJson(json);
@override
final String start;
@override
final String end;
@override
final int year;
@override
final String stateCode;
@override
final String name;
@override
final String slug;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Holiday(start: $start, end: $end, year: $year, stateCode: $stateCode, name: $name, slug: $slug)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Holiday'))
..add(DiagnosticsProperty('start', start))
..add(DiagnosticsProperty('end', end))
..add(DiagnosticsProperty('year', year))
..add(DiagnosticsProperty('stateCode', stateCode))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('slug', slug));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HolidayImpl &&
(identical(other.start, start) || other.start == start) &&
(identical(other.end, end) || other.end == end) &&
(identical(other.year, year) || other.year == year) &&
(identical(other.stateCode, stateCode) ||
other.stateCode == stateCode) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.slug, slug) || other.slug == slug));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, start, end, year, stateCode, name, slug);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$HolidayImplCopyWith<_$HolidayImpl> get copyWith =>
__$$HolidayImplCopyWithImpl<_$HolidayImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HolidayImplToJson(
this,
);
}
}
abstract class _Holiday implements Holiday {
const factory _Holiday(
{required final String start,
required final String end,
required final int year,
required final String stateCode,
required final String name,
required final String slug}) = _$HolidayImpl;
factory _Holiday.fromJson(Map<String, dynamic> json) = _$HolidayImpl.fromJson;
@override
String get start;
@override
String get end;
@override
int get year;
@override
String get stateCode;
@override
String get name;
@override
String get slug;
@override
@JsonKey(ignore: true)
_$$HolidayImplCopyWith<_$HolidayImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'holidays_state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HolidaysStateImpl _$$HolidaysStateImplFromJson(Map<String, dynamic> json) =>
_$HolidaysStateImpl(
showPastHolidays: json['showPastHolidays'] as bool,
showDisclaimer: json['showDisclaimer'] as bool,
holidays: (json['holidays'] as List<dynamic>)
.map((e) => Holiday.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$HolidaysStateImplToJson(_$HolidaysStateImpl instance) =>
<String, dynamic>{
'showPastHolidays': instance.showPastHolidays,
'showDisclaimer': instance.showDisclaimer,
'holidays': instance.holidays,
};
_$HolidayImpl _$$HolidayImplFromJson(Map<String, dynamic> json) =>
_$HolidayImpl(
start: json['start'] as String,
end: json['end'] as String,
year: json['year'] as int,
stateCode: json['stateCode'] as String,
name: json['name'] as String,
slug: json['slug'] as String,
);
Map<String, dynamic> _$$HolidayImplToJson(_$HolidayImpl instance) =>
<String, dynamic>{
'start': instance.start,
'end': instance.end,
'year': instance.year,
'stateCode': instance.stateCode,
'name': instance.name,
'slug': instance.slug,
};

View File

@ -0,0 +1,13 @@
import 'package:dio/dio.dart';
import '../../../basis/dataloader/holiday_data_loader.dart';
import '../../../infrastructure/dataLoader/data_loader.dart';
import '../bloc/holidays_state.dart';
class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> {
@override
List<Holiday> assemble(DataLoaderResult data) => data.asListOfMaps().map(Holiday.fromJson).toList();
@override
Future<Response<String>> fetch() => dio.get('/holidays/HE');
}

View File

@ -0,0 +1,7 @@
import '../../../infrastructure/repository/repository.dart';
import '../bloc/holidays_state.dart';
import '../dataProvider/holidays_get_holidays.dart';
class HolidaysRepository extends Repository<HolidaysState> {
Future<List<Holiday>> getHolidays() => HolidaysGetHolidays().run();
}

View File

@ -0,0 +1,120 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import '../../../../../view/pages/more/holidays/holidays.dart';
import '../../../../../widget/animatedTime.dart';
import '../../../../../widget/centeredLeading.dart';
import '../../../../../widget/debug/debugTile.dart';
import '../../../infrastructure/loadableState/loadable_state.dart';
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
import '../bloc/holidays_bloc.dart';
import '../bloc/holidays_event.dart';
import '../bloc/holidays_state.dart';
class HolidaysView extends StatelessWidget {
const HolidaysView({super.key});
@override
Widget build(BuildContext context) => BlocModule<HolidaysBloc, LoadableState<HolidaysState>>(
create: (contet) => HolidaysBloc(),
autoRebuild: true,
child: (context, bloc, state) {
log(state.toString());
void showDisclaimer() {
showDialog(context: context, builder: (context) => AlertDialog(
title: const Text('Richtigkeit und Bereitstellung der Daten'),
content: const Text(''
'Sämtliche Datumsangaben sind ohne Gewähr.\n'
'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n'
'Die Daten stammen von https://ferien-api.de/'),
actions: [
TextButton(child: const Text('Okay'), onPressed: () {
bloc.add(DisclaimerDismissed());
Navigator.of(context).pop();
}),
],
));
}
return Scaffold(
appBar: AppBar(
title: const Text('Schulferien in Hessen'),
actions: [
IconButton(
icon: const Icon(Icons.warning_amber_outlined),
onPressed: showDisclaimer,
),
PopupMenuButton<bool>(
initialValue: bloc.showPastHolidays(),
icon: const Icon(Icons.manage_history_outlined),
itemBuilder: (context) => [true, false].map((e) => PopupMenuItem<bool>(
value: e,
enabled: e != bloc.showPastHolidays(),
child: Row(
children: [
Icon(e ? Icons.history_outlined : Icons.history_toggle_off_outlined, color: Theme.of(context).colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? 'Alle anzeigen' : 'Nur zukünftige anzeigen')
],
)
)).toList(),
onSelected: (e) => bloc.add(SetPastHolidaysVisible(e)),
),
],
),
body: LoadableStateConsumer<HolidaysBloc, HolidaysState>(
child: (state, loading) => ListView.builder(
itemCount: state.holidays.length,
itemBuilder: (context, index) {
var holiday = state.holidays[index];
var holidayType = holiday.name.split(' ').first.capitalize();
String formatDate(String enDate) => Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy');
return ListTile(
leading: const CenteredLeading(Icon(Icons.calendar_month)),
title: Text('$holidayType ab ${formatDate(holiday.start)}'),
subtitle: Text('bis ${formatDate(holiday.end)}'),
onTap: () => showDialog(context: context, builder: (context) => SimpleDialog(
title: Text('$holidayType ${holiday.year} in Hessen'),
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.signpost_outlined)),
title: Text(holiday.name),
subtitle: Text(holiday.slug),
),
ListTile(
leading: const Icon(Icons.arrow_forward),
title: Text('vom ${formatDate(holiday.start)}'),
),
ListTile(
leading: const Icon(Icons.arrow_back),
title: Text('bis zum ${formatDate(holiday.end)}'),
),
Visibility(
visible: !DateTime.parse(holiday.start).difference(DateTime.now()).isNegative,
replacement: ListTile(
leading: const CenteredLeading(Icon(Icons.content_paste_search_outlined)),
title: Text(Jiffy.parse(holiday.start).fromNow()),
),
child: ListTile(
leading: const CenteredLeading(Icon(Icons.timer_outlined)),
title: AnimatedTime(callback: () => DateTime.parse(holiday.start).difference(DateTime.now())),
subtitle: Text(Jiffy.parse(holiday.start).fromNow()),
),
),
DebugTile(context).jsonData(holiday.toJson()),
],
)),
trailing: const Icon(Icons.arrow_right),
);
},
),
),
);
},
);
}

View File

@ -8,7 +8,7 @@ class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, Mar
@override @override
Future<void> gatherData() async { Future<void> gatherData() async {
var messages = await repo.getMessages(); var messages = await repo.getMessages();
add(Emit((state) => state.copyWith(messageList: messages))); add(DataGathered((state) => state.copyWith(messageList: messages)));
} }
@override @override

View File

@ -1,7 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../../infrastructure/dataLoader/data_loader.dart'; import '../../../infrastructure/dataLoader/data_loader.dart';
import '../../../infrastructure/dataLoader/mhsl_data_loader.dart'; import '../../../basis/dataloader/mhsl_data_loader.dart';
import '../bloc/marianum_message_state.dart'; import '../bloc/marianum_message_state.dart';
class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> { class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> {

View File

@ -2,7 +2,6 @@ import 'package:better_open_file/better_open_file.dart';
import 'package:bubble/bubble.dart'; import 'package:bubble/bubble.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
import 'package:flowder/flowder.dart'; import 'package:flowder/flowder.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';