From 634fe41e78f02e8c96fc4e6a3f25d338606408b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Tue, 14 May 2024 14:54:01 +0200 Subject: [PATCH 1/5] wip: bloc for holidays --- lib/api/holidays/getHolidays.dart | 6 +- lib/api/holidays/getHolidaysCache.dart | 9 +- .../basis/dataloader/holiday_data_loader.dart | 9 + .../dataloader}/mhsl_data_loader.dart | 2 +- .../dataLoader/data_loader.dart | 6 +- .../loadableState/loadable_state.dart | 10 +- .../view/loadable_state_error_bar.dart | 1 - .../loadable_hydrated_bloc.dart | 59 ++- .../loadable_hydrated_bloc_event.dart | 8 +- .../loadable_save_context.dart | 1 - lib/state/app/modules/app_modules.dart | 4 +- .../modules/holidays/bloc/holidays_bloc.dart | 36 ++ .../modules/holidays/bloc/holidays_event.dart | 9 + .../modules/holidays/bloc/holidays_state.dart | 30 ++ .../holidays/bloc/holidays_state.freezed.dart | 463 ++++++++++++++++++ .../holidays/bloc/holidays_state.g.dart | 43 ++ .../dataProvider/holidays_get_holidays.dart | 13 + .../repository/holidays_repository.dart | 7 + .../modules/holidays/view/holidays_view.dart | 120 +++++ .../bloc/marianum_message_bloc.dart | 2 +- .../marianum_message_get_messages.dart | 2 +- .../pages/talk/components/chatBubble.dart | 1 - .../talk/components/chatBubbleStyles.dart | 2 +- 23 files changed, 803 insertions(+), 40 deletions(-) create mode 100644 lib/state/app/basis/dataloader/holiday_data_loader.dart rename lib/state/app/{infrastructure/dataLoader => basis/dataloader}/mhsl_data_loader.dart (78%) create mode 100644 lib/state/app/modules/holidays/bloc/holidays_bloc.dart create mode 100644 lib/state/app/modules/holidays/bloc/holidays_event.dart create mode 100644 lib/state/app/modules/holidays/bloc/holidays_state.dart create mode 100644 lib/state/app/modules/holidays/bloc/holidays_state.freezed.dart create mode 100644 lib/state/app/modules/holidays/bloc/holidays_state.g.dart create mode 100644 lib/state/app/modules/holidays/dataProvider/holidays_get_holidays.dart create mode 100644 lib/state/app/modules/holidays/repository/holidays_repository.dart create mode 100644 lib/state/app/modules/holidays/view/holidays_view.dart diff --git a/lib/api/holidays/getHolidays.dart b/lib/api/holidays/getHolidays.dart index 1d7be0b..8ce3325 100644 --- a/lib/api/holidays/getHolidays.dart +++ b/lib/api/holidays/getHolidays.dart @@ -1,5 +1,4 @@ import 'dart:convert'; - import 'package:http/http.dart' as http; import 'getHolidaysResponse.dart'; @@ -7,11 +6,10 @@ import 'getHolidaysResponse.dart'; class GetHolidays { Future query() async { var response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body; + var data = jsonDecode(response) as List; return GetHolidaysResponse( List.from( - jsonDecode(response).map( - GetHolidaysResponseObject.fromJson - ) + data.map((e) => GetHolidaysResponseObject.fromJson(e as Map)) ) ); } diff --git a/lib/api/holidays/getHolidaysCache.dart b/lib/api/holidays/getHolidaysCache.dart index d27a9ed..49e04e1 100644 --- a/lib/api/holidays/getHolidaysCache.dart +++ b/lib/api/holidays/getHolidaysCache.dart @@ -13,12 +13,11 @@ class GetHolidaysCache extends RequestCache { GetHolidaysResponse onLocalData(String json) { List parsedListJson = jsonDecode(json)['data']; return GetHolidaysResponse( - List.from( - parsedListJson.map( - // ignore: unnecessary_lambdas - (dynamic i) => GetHolidaysResponseObject.fromJson(i) - ) + List.from( + parsedListJson.map( + (i) => GetHolidaysResponseObject.fromJson(i as Map) ) + ) ); } diff --git a/lib/state/app/basis/dataloader/holiday_data_loader.dart b/lib/state/app/basis/dataloader/holiday_data_loader.dart new file mode 100644 index 0000000..19c345f --- /dev/null +++ b/lib/state/app/basis/dataloader/holiday_data_loader.dart @@ -0,0 +1,9 @@ +import 'package:dio/dio.dart'; + +import '../../infrastructure/dataLoader/data_loader.dart'; + +abstract class HolidayDataLoader extends DataLoader { + HolidayDataLoader() : super(Dio(BaseOptions( + baseUrl: 'https://ferien-api.de/api/v1/', + ))); +} diff --git a/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart b/lib/state/app/basis/dataloader/mhsl_data_loader.dart similarity index 78% rename from lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart rename to lib/state/app/basis/dataloader/mhsl_data_loader.dart index 0825587..6b4baab 100644 --- a/lib/state/app/infrastructure/dataLoader/mhsl_data_loader.dart +++ b/lib/state/app/basis/dataloader/mhsl_data_loader.dart @@ -1,6 +1,6 @@ import 'package:dio/dio.dart'; -import 'data_loader.dart'; +import '../../infrastructure/dataLoader/data_loader.dart'; abstract class MhslDataLoader extends DataLoader { MhslDataLoader() : super(Dio(BaseOptions( diff --git a/lib/state/app/infrastructure/dataLoader/data_loader.dart b/lib/state/app/infrastructure/dataLoader/data_loader.dart index cb4a3fe..64c1aa7 100644 --- a/lib/state/app/infrastructure/dataLoader/data_loader.dart +++ b/lib/state/app/infrastructure/dataLoader/data_loader.dart @@ -35,8 +35,12 @@ abstract class DataLoader { } class DataLoaderResult { - final Map json; + final dynamic json; final Map headers; + Map asMap() => json as Map; + List asList() => json as List; + List> asListOfMaps() => asList().map((e) => e as Map).toList(); + DataLoaderResult({required this.json, required this.headers}); } diff --git a/lib/state/app/infrastructure/loadableState/loadable_state.dart b/lib/state/app/infrastructure/loadableState/loadable_state.dart index ac1626a..b163e41 100644 --- a/lib/state/app/infrastructure/loadableState/loadable_state.dart +++ b/lib/state/app/infrastructure/loadableState/loadable_state.dart @@ -9,11 +9,11 @@ class LoadableState with _$LoadableState { const LoadableState._(); const factory LoadableState({ - @Default(true) bool isLoading, - @Default(null) TState? data, - @Default(null) int? lastFetch, - @Default(null) void Function()? reFetch, - @Default(null) LoadingError? error, + required bool isLoading, + required TState? data, + required int? lastFetch, + required void Function()? reFetch, + required LoadingError? error, }) = _LoadableState; bool _hasError() => error != null; diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart index bc5353c..fd27b2b 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_error_bar.dart @@ -89,4 +89,3 @@ class _LoadableStateErrorBarTextState extends State { super.dispose(); } } - diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart index 116bad2..cdcedf7 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart @@ -17,23 +17,40 @@ abstract class LoadableHydratedBloc< LoadableState > { late TRepository _repository; - LoadableHydratedBloc() : super(const LoadableState()) { + LoadableHydratedBloc() : super(const LoadableState( + error: null, + data: null, + isLoading: true, + lastFetch: null, + reFetch: null, + )) { - on>((event, emit) => emit(LoadableState( - isLoading: event.loading, + on>((event, emit) { + emit(LoadableState( + isLoading: state.isLoading, data: event.state(innerState ?? fromNothing()), - lastFetch: DateTime.now().millisecondsSinceEpoch, - reFetch: retry + lastFetch: state.lastFetch, + reFetch: retry, + error: state.error, + )); + }); + + on>((event, emit) => emit(LoadableState( + isLoading: false, + data: event.state(innerState ?? fromNothing()), + lastFetch: DateTime.now().millisecondsSinceEpoch, + reFetch: retry, + error: null, ))); on>((event, emit) => emit(LoadableState( isLoading: true, data: innerState, - lastFetch: state.lastFetch + lastFetch: state.lastFetch, + reFetch: null, + error: null, ))); - on>((event, emit) => emit(const LoadableState())); - on>((event, emit) => emit(LoadableState( isLoading: false, data: innerState, @@ -61,7 +78,7 @@ abstract class LoadableHydratedBloc< (e) { log('Error while fetching ${TState.toString()}: ${e.toString()}'); add(Error(LoadingError( - message: e.message, + message: e.message ?? e.toString(), allowRetry: true, ))); }, @@ -73,14 +90,30 @@ abstract class LoadableHydratedBloc< @override fromJson(Map 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 - Map? toJson(LoadableState state) => LoadableSaveContext.wrap( - toStorage(state.data), + Map? toJson(LoadableState state) { + Map? 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 - ); + ); + } Future gatherData(); TRepository repository(); diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart index 06462de..55b8e6a 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart @@ -3,10 +3,12 @@ import '../../loadableState/loading_error.dart'; class LoadableHydratedBlocEvent {} class Emit extends LoadableHydratedBlocEvent { final TState Function(TState state) state; - final bool loading; - Emit(this.state, {this.loading = false}); + Emit(this.state); +} +class DataGathered extends LoadableHydratedBlocEvent { + final TState Function(TState state) state; + DataGathered(this.state); } -class ClearState extends LoadableHydratedBlocEvent {} class Error extends LoadableHydratedBlocEvent { final LoadingError error; Error(this.error); diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart index e4d68d5..0dea5cd 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_save_context.dart @@ -21,4 +21,3 @@ class LoadableSaveContext with _$LoadableSaveContext { static ({Map data, LoadableSaveContext meta}) unwrap(Map data) => (data: data[dataKey] as Map, meta: LoadableSaveContext.fromJson(data[metaKey])); } - diff --git a/lib/state/app/modules/app_modules.dart b/lib/state/app/modules/app_modules.dart index 83b93b5..5e53b32 100644 --- a/lib/state/app/modules/app_modules.dart +++ b/lib/state/app/modules/app_modules.dart @@ -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 '../../../model/breakers/Breaker.dart'; import '../../../view/pages/files/files.dart'; -import '../../../view/pages/more/holidays/holidays.dart'; import '../../../view/pages/more/roomplan/roomplan.dart'; import '../../../view/pages/talk/chatList.dart'; import '../../../view/pages/timetable/timetable.dart'; import '../../../widget/centeredLeading.dart'; import 'gradeAverages/view/grade_averages_view.dart'; +import 'holidays/view/holidays_view.dart'; import 'marianumMessage/view/marianum_message_list_view.dart'; class AppModule { @@ -26,7 +26,7 @@ class AppModule { Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.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]!; diff --git a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart new file mode 100644 index 0000000..cd369f0 --- /dev/null +++ b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart @@ -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 { + HolidaysBloc() { + on((event, emit) { + log('set pastholidays: ${event.shouldBeVisible.toString()}'); + add(Emit((state) => state.copyWith(showPastHolidays: state.showPastHolidays))); + }); + + on((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 json) => HolidaysState.fromJson(json); + @override + Future gatherData() async { + var holidays = await repo.getHolidays(); + add(DataGathered((state) => state.copyWith(holidays: holidays))); + } + @override + repository() => HolidaysRepository(); + @override + Map? toStorage(state) => state.toJson(); +} diff --git a/lib/state/app/modules/holidays/bloc/holidays_event.dart b/lib/state/app/modules/holidays/bloc/holidays_event.dart new file mode 100644 index 0000000..8565250 --- /dev/null +++ b/lib/state/app/modules/holidays/bloc/holidays_event.dart @@ -0,0 +1,9 @@ +import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart'; +import 'holidays_state.dart'; + +sealed class HolidaysEvent extends LoadableHydratedBlocEvent {} +class SetPastHolidaysVisible extends HolidaysEvent { + final bool shouldBeVisible; + SetPastHolidaysVisible(this.shouldBeVisible); +} +class DisclaimerDismissed extends HolidaysEvent {} diff --git a/lib/state/app/modules/holidays/bloc/holidays_state.dart b/lib/state/app/modules/holidays/bloc/holidays_state.dart new file mode 100644 index 0000000..05c0cd2 --- /dev/null +++ b/lib/state/app/modules/holidays/bloc/holidays_state.dart @@ -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 holidays, + }) = _HolidaysState; + + factory HolidaysState.fromJson(Map 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 json) => _$HolidayFromJson(json); +} diff --git a/lib/state/app/modules/holidays/bloc/holidays_state.freezed.dart b/lib/state/app/modules/holidays/bloc/holidays_state.freezed.dart new file mode 100644 index 0000000..6659f45 --- /dev/null +++ b/lib/state/app/modules/holidays/bloc/holidays_state.freezed.dart @@ -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 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 json) { + return _HolidaysState.fromJson(json); +} + +/// @nodoc +mixin _$HolidaysState { + bool get showPastHolidays => throw _privateConstructorUsedError; + bool get showDisclaimer => throw _privateConstructorUsedError; + List get holidays => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $HolidaysStateCopyWith 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 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, + ) 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 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, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$HolidaysStateImpl + with DiagnosticableTreeMixin + implements _HolidaysState { + const _$HolidaysStateImpl( + {required this.showPastHolidays, + required this.showDisclaimer, + required final List holidays}) + : _holidays = holidays; + + factory _$HolidaysStateImpl.fromJson(Map json) => + _$$HolidaysStateImplFromJson(json); + + @override + final bool showPastHolidays; + @override + final bool showDisclaimer; + final List _holidays; + @override + List 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 toJson() { + return _$$HolidaysStateImplToJson( + this, + ); + } +} + +abstract class _HolidaysState implements HolidaysState { + const factory _HolidaysState( + {required final bool showPastHolidays, + required final bool showDisclaimer, + required final List holidays}) = _$HolidaysStateImpl; + + factory _HolidaysState.fromJson(Map json) = + _$HolidaysStateImpl.fromJson; + + @override + bool get showPastHolidays; + @override + bool get showDisclaimer; + @override + List get holidays; + @override + @JsonKey(ignore: true) + _$$HolidaysStateImplCopyWith<_$HolidaysStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Holiday _$HolidayFromJson(Map 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 toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $HolidayCopyWith 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 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 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 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; +} diff --git a/lib/state/app/modules/holidays/bloc/holidays_state.g.dart b/lib/state/app/modules/holidays/bloc/holidays_state.g.dart new file mode 100644 index 0000000..1d0f3f0 --- /dev/null +++ b/lib/state/app/modules/holidays/bloc/holidays_state.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'holidays_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$HolidaysStateImpl _$$HolidaysStateImplFromJson(Map json) => + _$HolidaysStateImpl( + showPastHolidays: json['showPastHolidays'] as bool, + showDisclaimer: json['showDisclaimer'] as bool, + holidays: (json['holidays'] as List) + .map((e) => Holiday.fromJson(e as Map)) + .toList(), + ); + +Map _$$HolidaysStateImplToJson(_$HolidaysStateImpl instance) => + { + 'showPastHolidays': instance.showPastHolidays, + 'showDisclaimer': instance.showDisclaimer, + 'holidays': instance.holidays, + }; + +_$HolidayImpl _$$HolidayImplFromJson(Map 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 _$$HolidayImplToJson(_$HolidayImpl instance) => + { + 'start': instance.start, + 'end': instance.end, + 'year': instance.year, + 'stateCode': instance.stateCode, + 'name': instance.name, + 'slug': instance.slug, + }; diff --git a/lib/state/app/modules/holidays/dataProvider/holidays_get_holidays.dart b/lib/state/app/modules/holidays/dataProvider/holidays_get_holidays.dart new file mode 100644 index 0000000..cd52128 --- /dev/null +++ b/lib/state/app/modules/holidays/dataProvider/holidays_get_holidays.dart @@ -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> { + @override + List assemble(DataLoaderResult data) => data.asListOfMaps().map(Holiday.fromJson).toList(); + + @override + Future> fetch() => dio.get('/holidays/HE'); +} diff --git a/lib/state/app/modules/holidays/repository/holidays_repository.dart b/lib/state/app/modules/holidays/repository/holidays_repository.dart new file mode 100644 index 0000000..72ec949 --- /dev/null +++ b/lib/state/app/modules/holidays/repository/holidays_repository.dart @@ -0,0 +1,7 @@ +import '../../../infrastructure/repository/repository.dart'; +import '../bloc/holidays_state.dart'; +import '../dataProvider/holidays_get_holidays.dart'; + +class HolidaysRepository extends Repository { + Future> getHolidays() => HolidaysGetHolidays().run(); +} diff --git a/lib/state/app/modules/holidays/view/holidays_view.dart b/lib/state/app/modules/holidays/view/holidays_view.dart new file mode 100644 index 0000000..3b40f4e --- /dev/null +++ b/lib/state/app/modules/holidays/view/holidays_view.dart @@ -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>( + 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( + initialValue: bloc.showPastHolidays(), + icon: const Icon(Icons.manage_history_outlined), + itemBuilder: (context) => [true, false].map((e) => PopupMenuItem( + 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( + 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), + ); + }, + ), + ), + ); + }, + ); +} diff --git a/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart b/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart index 7876351..97c3b95 100644 --- a/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart +++ b/lib/state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart @@ -8,7 +8,7 @@ class MarianumMessageBloc extends LoadableHydratedBloc gatherData() async { var messages = await repo.getMessages(); - add(Emit((state) => state.copyWith(messageList: messages))); + add(DataGathered((state) => state.copyWith(messageList: messages))); } @override diff --git a/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart b/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart index cb7f9db..f8c4b24 100644 --- a/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart +++ b/lib/state/app/modules/marianumMessage/dataProvider/marianum_message_get_messages.dart @@ -1,7 +1,7 @@ import 'package:dio/dio.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'; class MarianumMessageGetMessages extends MhslDataLoader { diff --git a/lib/view/pages/talk/components/chatBubble.dart b/lib/view/pages/talk/components/chatBubble.dart index 8110c1c..0539f19 100644 --- a/lib/view/pages/talk/components/chatBubble.dart +++ b/lib/view/pages/talk/components/chatBubble.dart @@ -2,7 +2,6 @@ import 'package:better_open_file/better_open_file.dart'; import 'package:bubble/bubble.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis; import 'package:flowder/flowder.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/view/pages/talk/components/chatBubbleStyles.dart b/lib/view/pages/talk/components/chatBubbleStyles.dart index c640ca2..5cf527a 100644 --- a/lib/view/pages/talk/components/chatBubbleStyles.dart +++ b/lib/view/pages/talk/components/chatBubbleStyles.dart @@ -51,4 +51,4 @@ class ChatBubbleStyles { alignment: Alignment.topRight, ); } -} \ No newline at end of file +} From a33c4ddac57582f68529f6a2e493bd4890ca5c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Mon, 27 May 2024 22:28:42 +0200 Subject: [PATCH 2/5] wip: fixed state not updating correctly --- .../modules/holidays/bloc/holidays_bloc.dart | 7 +- .../modules/holidays/view/holidays_view.dart | 100 ++++++++---------- lib/widget/list_view_util.dart | 9 ++ 3 files changed, 60 insertions(+), 56 deletions(-) create mode 100644 lib/widget/list_view_util.dart diff --git a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart index cd369f0..033be9e 100644 --- a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart +++ b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart @@ -9,8 +9,8 @@ import 'holidays_state.dart'; class HolidaysBloc extends LoadableHydratedBloc { HolidaysBloc() { on((event, emit) { - log('set pastholidays: ${event.shouldBeVisible.toString()}'); - add(Emit((state) => state.copyWith(showPastHolidays: state.showPastHolidays))); + log('SetPastHolidaysVisible: ${event.shouldBeVisible.toString()}'); + add(Emit((state) => state.copyWith(showPastHolidays: event.shouldBeVisible))); }); on((event, emit) => add( @@ -19,6 +19,9 @@ class HolidaysBloc extends LoadableHydratedBloc innerState?.showPastHolidays ?? false; + List? getHolidays() => innerState?.holidays.where( + (element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()) + ).toList(); @override fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true); diff --git a/lib/state/app/modules/holidays/view/holidays_view.dart b/lib/state/app/modules/holidays/view/holidays_view.dart index 3b40f4e..45b0b7f 100644 --- a/lib/state/app/modules/holidays/view/holidays_view.dart +++ b/lib/state/app/modules/holidays/view/holidays_view.dart @@ -1,13 +1,11 @@ -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/list_view_util.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'; @@ -18,18 +16,17 @@ class HolidaysView extends StatelessWidget { const HolidaysView({super.key}); @override - Widget build(BuildContext context) => BlocModule>( - create: (contet) => HolidaysBloc(), + Widget build(BuildContext context) => BlocModule( + create: (context) => 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/'), + '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()); @@ -38,7 +35,7 @@ class HolidaysView extends StatelessWidget { ], )); } - + return Scaffold( appBar: AppBar( title: const Text('Schulferien in Hessen'), @@ -66,53 +63,48 @@ class HolidaysView extends StatelessWidget { ], ), body: LoadableStateConsumer( - child: (state, loading) => ListView.builder( - itemCount: state.holidays.length, - itemBuilder: (context, index) { - var holiday = state.holidays[index]; - var holidayType = holiday.name.split(' ').first.capitalize(); + child: (state, loading) => ListViewUtil.fromList(bloc.getHolidays(), (holiday) { + var holidayType = holiday.name.split(' ').first.capitalize(); + String formatDate(String enDate) => Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy'); - 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), + return ListTile( + leading: const CenteredLeading(Icon(Icons.calendar_month)), + title: Text('${state.showPastHolidays}$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()), ), - ListTile( - leading: const Icon(Icons.arrow_forward), - title: Text('vom ${formatDate(holiday.start)}'), + 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()), ), - 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), - ); - }, - ), + ), + DebugTile(context).jsonData(holiday.toJson()), + ], + )), + trailing: const Icon(Icons.arrow_right), + ); + }), ), ); }, diff --git a/lib/widget/list_view_util.dart b/lib/widget/list_view_util.dart new file mode 100644 index 0000000..468ae95 --- /dev/null +++ b/lib/widget/list_view_util.dart @@ -0,0 +1,9 @@ + +import 'package:flutter/material.dart'; + +class ListViewUtil { + static ListView fromList(List? items, Widget Function(T item) map) => ListView.builder( + itemCount: items?.length ?? 0, + itemBuilder: (context, index) => items != null ? map(items[index]) : null, + ); +} From fe93a94fc657665e1d9d9087dbf448d0f98e9503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Wed, 12 Jun 2024 15:53:13 +0200 Subject: [PATCH 3/5] bloc for holidays --- .../view/loadable_state_consumer.dart | 3 +- .../utilityWidgets/bloc_module.dart | 9 +- lib/state/app/modules/app_modules.dart | 2 +- .../modules/holidays/bloc/holidays_bloc.dart | 9 +- .../modules/holidays/view/holidays_view.dart | 25 +-- .../pages/more/feedback/feedbackDialog.dart | 7 +- lib/view/pages/more/holidays/holidays.dart | 157 ------------------ lib/widget/string_extensions.dart | 3 + 8 files changed, 37 insertions(+), 178 deletions(-) delete mode 100644 lib/view/pages/more/holidays/holidays.dart create mode 100644 lib/widget/string_extensions.dart diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart index 7d9244e..238ab94 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart @@ -15,8 +15,9 @@ import 'loadable_state_primary_loading.dart'; class LoadableStateConsumer, LoadableState>, TState> extends StatelessWidget { final Widget Function(TState state, bool loading) child; + final void Function(TState state)? onLoad; final bool wrapWithScrollView; - const LoadableStateConsumer({required this.child, this.wrapWithScrollView = false, super.key}); + const LoadableStateConsumer({required this.child, this.onLoad, this.wrapWithScrollView = false, super.key}); static Duration animationDuration = const Duration(milliseconds: 200); diff --git a/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart b/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart index 35297b7..fa1bee7 100644 --- a/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart +++ b/lib/state/app/infrastructure/utilityWidgets/bloc_module.dart @@ -5,14 +5,19 @@ class BlocModule, TState> extends St final TBloc Function(BuildContext context) create; final Widget Function(BuildContext context, TBloc bloc, TState state) child; final bool autoRebuild; - const BlocModule({required this.create, required this.child, this.autoRebuild = false, super.key}); + final void Function(BuildContext context, TBloc bloc)? onInitialisation; + const BlocModule({required this.create, required this.child, this.autoRebuild = false, this.onInitialisation, super.key}); Widget rebuildChild(BuildContext context) => child(context, context.watch(), context.watch().state); Widget staticChild(BuildContext context) => child(context, context.read(), context.read().state); @override Widget build(BuildContext context) => BlocProvider( - create: create, + create: (context) { + var bloc = create(context); + this.onInitialisation != null ? this.onInitialisation!(context, bloc) : null; + return bloc; + }, child: Builder( builder: (context) => autoRebuild ? rebuildChild(context) diff --git a/lib/state/app/modules/app_modules.dart b/lib/state/app/modules/app_modules.dart index 5e53b32..df84651 100644 --- a/lib/state/app/modules/app_modules.dart +++ b/lib/state/app/modules/app_modules.dart @@ -26,7 +26,7 @@ class AppModule { Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), - Modules.holidays: AppModule('Schulferien', Icons.holiday_village, HolidaysView.new), + Modules.holidays: AppModule('Schulferien', Icons.time_to_leave, HolidaysView.new), }; static AppModule getModule(Modules module) => modules()[module]!; diff --git a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart index 033be9e..248a080 100644 --- a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart +++ b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart @@ -1,5 +1,3 @@ -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'; @@ -9,7 +7,6 @@ import 'holidays_state.dart'; class HolidaysBloc extends LoadableHydratedBloc { HolidaysBloc() { on((event, emit) { - log('SetPastHolidaysVisible: ${event.shouldBeVisible.toString()}'); add(Emit((state) => state.copyWith(showPastHolidays: event.shouldBeVisible))); }); @@ -19,9 +16,9 @@ class HolidaysBloc extends LoadableHydratedBloc innerState?.showPastHolidays ?? false; - List? getHolidays() => innerState?.holidays.where( - (element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()) - ).toList(); + List? getHolidays() => innerState?.holidays + .where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now())) + .toList() ?? []; @override fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true); diff --git a/lib/state/app/modules/holidays/view/holidays_view.dart b/lib/state/app/modules/holidays/view/holidays_view.dart index 45b0b7f..d0da804 100644 --- a/lib/state/app/modules/holidays/view/holidays_view.dart +++ b/lib/state/app/modules/holidays/view/holidays_view.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import '../../../../../view/pages/more/holidays/holidays.dart'; import '../../../../../widget/animatedTime.dart'; import '../../../../../widget/list_view_util.dart'; import '../../../../../widget/centeredLeading.dart'; import '../../../../../widget/debug/debugTile.dart'; +import '../../../../../widget/string_extensions.dart'; import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart'; import '../../../infrastructure/utilityWidgets/bloc_module.dart'; import '../bloc/holidays_bloc.dart'; @@ -41,12 +41,12 @@ class HolidaysView extends StatelessWidget { title: const Text('Schulferien in Hessen'), actions: [ IconButton( - icon: const Icon(Icons.warning_amber_outlined), + icon: const Icon(Icons.info_outline), onPressed: showDisclaimer, ), PopupMenuButton( initialValue: bloc.showPastHolidays(), - icon: const Icon(Icons.manage_history_outlined), + icon: const Icon(Icons.history), itemBuilder: (context) => [true, false].map((e) => PopupMenuItem( value: e, enabled: e != bloc.showPastHolidays(), @@ -65,26 +65,31 @@ class HolidaysView extends StatelessWidget { body: LoadableStateConsumer( child: (state, loading) => ListViewUtil.fromList(bloc.getHolidays(), (holiday) { var holidayType = holiday.name.split(' ').first.capitalize(); - String formatDate(String enDate) => Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy'); + String formatDate(String date) => Jiffy.parse(date).format(pattern: 'dd.MM.yyyy'); + String getYear(String date, {String format = 'yyyy'}) => Jiffy.parse(date).format(pattern: format); + + String getHolidayYear(String startDate, String endDate) => getYear(startDate) == getYear(endDate) + ? getYear(startDate) + : '${getYear(startDate)}/${getYear(endDate, format: 'yy')}'; return ListTile( leading: const CenteredLeading(Icon(Icons.calendar_month)), - title: Text('${state.showPastHolidays}$holidayType ab ${formatDate(holiday.start)}'), - subtitle: Text('bis ${formatDate(holiday.end)}'), + title: Text('$holidayType ${getHolidayYear(holiday.start, holiday.end)}'), + subtitle: Text('${formatDate(holiday.start)} - ${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), + title: Text(holiday.name.capitalize()), + subtitle: Text(holiday.slug.capitalize()), ), ListTile( - leading: const Icon(Icons.arrow_forward), + leading: const Icon(Icons.date_range_outlined), title: Text('vom ${formatDate(holiday.start)}'), ), ListTile( - leading: const Icon(Icons.arrow_back), + leading: const Icon(Icons.date_range_outlined), title: Text('bis zum ${formatDate(holiday.end)}'), ), Visibility( diff --git a/lib/view/pages/more/feedback/feedbackDialog.dart b/lib/view/pages/more/feedback/feedbackDialog.dart index c3b6d15..f78eea2 100644 --- a/lib/view/pages/more/feedback/feedbackDialog.dart +++ b/lib/view/pages/more/feedback/feedbackDialog.dart @@ -60,12 +60,17 @@ class _FeedbackDialogState extends State { Padding( padding: const EdgeInsets.all(10), child: TextField( + onChanged: (value) { + if(value.trim().toLowerCase() == 'ranzig') { + _feedbackInput.text = 'selber'; + } + }, controller: _feedbackInput, autofocus: true, decoration: InputDecoration( border: const OutlineInputBorder(), label: const Text('Feedback und Verbesserungen'), - errorText: _textFieldEmpty ? 'Bitte gib eine Beschreibung an' : null, + errorText: _textFieldEmpty ? 'Bitte gib eine Beschreibung an???' : null, ), minLines: 4, maxLines: 7, diff --git a/lib/view/pages/more/holidays/holidays.dart b/lib/view/pages/more/holidays/holidays.dart deleted file mode 100644 index 6d5df2a..0000000 --- a/lib/view/pages/more/holidays/holidays.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'dart:core'; - -import 'package:flutter/material.dart'; -import 'package:jiffy/jiffy.dart'; -import 'package:provider/provider.dart'; - -import '../../../../model/holidays/holidaysProps.dart'; -import '../../../../storage/base/settingsProvider.dart'; -import '../../../../widget/centeredLeading.dart'; -import '../../../../widget/confirmDialog.dart'; -import '../../../../widget/debug/debugTile.dart'; -import '../../../../widget/loadingSpinner.dart'; -import '../../../../widget/placeholderView.dart'; -import '../../../../widget/animatedTime.dart'; - -class Holidays extends StatefulWidget { - const Holidays({super.key}); - - @override - State createState() => _HolidaysState(); -} - -extension StringExtension on String { - String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; -} - -class _HolidaysState extends State { - late SettingsProvider settings = Provider.of(context, listen: false); - late bool showPastEvents = settings.val().holidaysSettings.showPastEvents; - - @override - void initState() { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Provider.of(context, listen: false).run(); - if(!settings.val().holidaysSettings.dismissedDisclaimer) showDisclaimer(); - }); - - super.initState(); - } - - String parseString(String enDate) => Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy'); - - void showDisclaimer() { - showDialog(context: context, builder: (context) => AlertDialog( - title: const Text('Richtigkeit und Bereitstellung der Daten'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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/'), - const SizedBox(height: 30), - ListTile( - title: const Text('Diese Meldung nicht mehr anzeigen'), - trailing: Checkbox( - value: settings.val().holidaysSettings.dismissedDisclaimer, - onChanged: (value) => settings.val(write: true).holidaysSettings.dismissedDisclaimer = value!, - ), - ) - ], - ), - actions: [ - TextButton(child: const Text('ferien-api.de besuchen'), onPressed: () => ConfirmDialog.openBrowser(context, 'https://ferien-api.de/')), - TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()), - ], - )); - } - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('Schulferien in Hessen'), - actions: [ - IconButton( - icon: const Icon(Icons.warning_amber_outlined), - onPressed: showDisclaimer, - ), - PopupMenuButton( - initialValue: settings.val().holidaysSettings.showPastEvents, - icon: const Icon(Icons.manage_history_outlined), - itemBuilder: (context) => [true, false].map((e) => PopupMenuItem( - value: e, - enabled: e != showPastEvents, - 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) { - setState(() { - showPastEvents = e; - settings.val(write: true).holidaysSettings.showPastEvents = e; - }); - }, - ), - ], - ), - body: Consumer(builder: (context, value, child) { - if(value.primaryLoading()) return const LoadingSpinner(); - - var holidays = value.getHolidaysResponse.data; - if(!showPastEvents) holidays = holidays.where((element) => DateTime.parse(element.end).isAfter(DateTime.now())).toList(); - - if(holidays.isEmpty) return const PlaceholderView(icon: Icons.search_off, text: 'Es wurden keine Ferieneinträge gefunden!'); - - return ListView.builder( - itemCount: holidays.length, - itemBuilder: (context, index) { - var holiday = holidays[index]; - var holidayType = holiday.name.split(' ').first.capitalize(); - return ListTile( - leading: const CenteredLeading(Icon(Icons.calendar_month)), - title: Text('$holidayType ab ${parseString(holiday.start)}'), - subtitle: Text('bis ${parseString(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 ${parseString(holiday.start)}'), - ), - ListTile( - leading: const Icon(Icons.arrow_back), - title: Text('bis zum ${parseString(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), - ); - }, - ); - }, - ) - ); -} diff --git a/lib/widget/string_extensions.dart b/lib/widget/string_extensions.dart new file mode 100644 index 0000000..246906a --- /dev/null +++ b/lib/widget/string_extensions.dart @@ -0,0 +1,3 @@ +extension StringExtensions on String { + String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; +} \ No newline at end of file From c443a1d567b8bb6569c280d59bce920edf3d933a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 23 Jun 2024 20:31:43 +0200 Subject: [PATCH 4/5] fixed disclaimer not showing on first visit --- .../loadableState/view/loadable_state_consumer.dart | 5 +++++ .../loadableHydratedBloc/loadable_hydrated_bloc.dart | 3 +-- lib/state/app/modules/app_modules.dart | 2 +- .../app/modules/holidays/bloc/holidays_bloc.dart | 1 + .../app/modules/holidays/view/holidays_view.dart | 12 +++++++----- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart index 238ab94..b0cdc65 100644 --- a/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart +++ b/lib/state/app/infrastructure/loadableState/view/loadable_state_consumer.dart @@ -24,6 +24,11 @@ class LoadableStateConsumer().state; + + if(!loadableState.isLoading && onLoad != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) => onLoad!(loadableState.data)); + } + var childWidget = ConditionalWrapper( condition: loadableState.reFetch != null, wrapper: (child) => RefreshIndicator( diff --git a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart index cdcedf7..66e20d6 100644 --- a/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart +++ b/lib/state/app/infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart @@ -103,10 +103,9 @@ abstract class LoadableHydratedBloc< Map? toJson(LoadableState state) { Map? data; try { - data = toStorage(state.data); + data = state.data == null ? null : toStorage(state.data); } catch(e) { log('Failed to save state ${TState.toString()}: ${e.toString()}'); - data = null; } return LoadableSaveContext.wrap( diff --git a/lib/state/app/modules/app_modules.dart b/lib/state/app/modules/app_modules.dart index df84651..796cfe6 100644 --- a/lib/state/app/modules/app_modules.dart +++ b/lib/state/app/modules/app_modules.dart @@ -26,7 +26,7 @@ class AppModule { Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), - Modules.holidays: AppModule('Schulferien', Icons.time_to_leave, HolidaysView.new), + Modules.holidays: AppModule('Schulferien', Icons.flight, HolidaysView.new), }; static AppModule getModule(Modules module) => modules()[module]!; diff --git a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart index 248a080..3f82d04 100644 --- a/lib/state/app/modules/holidays/bloc/holidays_bloc.dart +++ b/lib/state/app/modules/holidays/bloc/holidays_bloc.dart @@ -16,6 +16,7 @@ class HolidaysBloc extends LoadableHydratedBloc innerState?.showPastHolidays ?? false; + bool showDisclaimerOnEntry() => innerState?.showDisclaimer ?? false; List? getHolidays() => innerState?.holidays .where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now())) .toList() ?? []; diff --git a/lib/state/app/modules/holidays/view/holidays_view.dart b/lib/state/app/modules/holidays/view/holidays_view.dart index d0da804..41be1f3 100644 --- a/lib/state/app/modules/holidays/view/holidays_view.dart +++ b/lib/state/app/modules/holidays/view/holidays_view.dart @@ -6,6 +6,7 @@ import '../../../../../widget/list_view_util.dart'; import '../../../../../widget/centeredLeading.dart'; import '../../../../../widget/debug/debugTile.dart'; import '../../../../../widget/string_extensions.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'; @@ -16,7 +17,7 @@ class HolidaysView extends StatelessWidget { const HolidaysView({super.key}); @override - Widget build(BuildContext context) => BlocModule( + Widget build(BuildContext context) => BlocModule>( create: (context) => HolidaysBloc(), autoRebuild: true, child: (context, bloc, state) { @@ -28,10 +29,7 @@ class HolidaysView extends StatelessWidget { '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(); - }), + TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()), ], )); } @@ -63,6 +61,10 @@ class HolidaysView extends StatelessWidget { ], ), body: LoadableStateConsumer( + onLoad: (state) { + if(state.showDisclaimer) showDisclaimer(); + bloc.add(DisclaimerDismissed()); + }, child: (state, loading) => ListViewUtil.fromList(bloc.getHolidays(), (holiday) { var holidayType = holiday.name.split(' ').first.capitalize(); String formatDate(String date) => Jiffy.parse(date).format(pattern: 'dd.MM.yyyy'); From c8e31b896b4c0dd4d733dba0581d442bbbfeb135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 23 Jun 2024 22:59:26 +0200 Subject: [PATCH 5/5] added eof linebreak --- lib/widget/string_extensions.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widget/string_extensions.dart b/lib/widget/string_extensions.dart index 246906a..21c3901 100644 --- a/lib/widget/string_extensions.dart +++ b/lib/widget/string_extensions.dart @@ -1,3 +1,3 @@ extension StringExtensions on String { String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; -} \ No newline at end of file +}