bloc for holidays

This commit is contained in:
Elias Müller 2024-06-12 15:53:13 +02:00
parent a33c4ddac5
commit fe93a94fc6
8 changed files with 37 additions and 178 deletions

View File

@ -15,8 +15,9 @@ import 'loadable_state_primary_loading.dart';
class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, 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);

View File

@ -5,14 +5,19 @@ class BlocModule<TBloc extends StateStreamableSource<TState>, 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<TBloc>(), context.watch<TBloc>().state);
Widget staticChild(BuildContext context) => child(context, context.read<TBloc>(), context.read<TBloc>().state);
@override
Widget build(BuildContext context) => BlocProvider<TBloc>(
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)

View File

@ -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]!;

View File

@ -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<HolidaysEvent, HolidaysState, HolidaysRepository> {
HolidaysBloc() {
on<SetPastHolidaysVisible>((event, emit) {
log('SetPastHolidaysVisible: ${event.shouldBeVisible.toString()}');
add(Emit((state) => state.copyWith(showPastHolidays: event.shouldBeVisible)));
});
@ -19,9 +16,9 @@ class HolidaysBloc extends LoadableHydratedBloc<HolidaysEvent, HolidaysState, Ho
}
bool showPastHolidays() => innerState?.showPastHolidays ?? false;
List<Holiday>? getHolidays() => innerState?.holidays.where(
(element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now())
).toList();
List<Holiday>? getHolidays() => innerState?.holidays
.where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()))
.toList() ?? [];
@override
fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true);

View File

@ -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<bool>(
initialValue: bloc.showPastHolidays(),
icon: const Icon(Icons.manage_history_outlined),
icon: const Icon(Icons.history),
itemBuilder: (context) => [true, false].map((e) => PopupMenuItem<bool>(
value: e,
enabled: e != bloc.showPastHolidays(),
@ -65,26 +65,31 @@ class HolidaysView extends StatelessWidget {
body: LoadableStateConsumer<HolidaysBloc, HolidaysState>(
child: (state, loading) => ListViewUtil.fromList<Holiday>(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(

View File

@ -60,12 +60,17 @@ class _FeedbackDialogState extends State<FeedbackDialog> {
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,

View File

@ -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<Holidays> createState() => _HolidaysState();
}
extension StringExtension on String {
String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
}
class _HolidaysState extends State<Holidays> {
late SettingsProvider settings = Provider.of<SettingsProvider>(context, listen: false);
late bool showPastEvents = settings.val().holidaysSettings.showPastEvents;
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<HolidaysProps>(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<bool>(
initialValue: settings.val().holidaysSettings.showPastEvents,
icon: const Icon(Icons.manage_history_outlined),
itemBuilder: (context) => [true, false].map((e) => PopupMenuItem<bool>(
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<HolidaysProps>(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),
);
},
);
},
)
);
}

View File

@ -0,0 +1,3 @@
extension StringExtensions on String {
String capitalize() => '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
}