claude refactor
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../theming/darkAppTheme.dart';
|
||||
|
||||
enum CustomTimetableColors { orange, red, green, blue }
|
||||
|
||||
class TimetableColors {
|
||||
static const CustomTimetableColors defaultColor = CustomTimetableColors.orange;
|
||||
|
||||
static ColorModeDisplay getDisplayOptions(CustomTimetableColors color) {
|
||||
switch (color) {
|
||||
case CustomTimetableColors.green:
|
||||
return ColorModeDisplay(color: Colors.green, displayName: 'Grün');
|
||||
case CustomTimetableColors.blue:
|
||||
return ColorModeDisplay(color: Colors.blue, displayName: 'Blau');
|
||||
case CustomTimetableColors.orange:
|
||||
return ColorModeDisplay(color: Colors.orange.shade800, displayName: 'Orange');
|
||||
case CustomTimetableColors.red:
|
||||
return ColorModeDisplay(color: DarkAppTheme.marianumRed, displayName: 'Rot');
|
||||
}
|
||||
}
|
||||
|
||||
static Color getColorFromString(String color) =>
|
||||
getDisplayOptions(CustomTimetableColors.values.firstWhere(
|
||||
(e) => e.name == color,
|
||||
orElse: () => defaultColor,
|
||||
)).color;
|
||||
}
|
||||
|
||||
class ColorModeDisplay {
|
||||
final Color color;
|
||||
final String displayName;
|
||||
|
||||
ColorModeDisplay({required this.color, required this.displayName});
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:rrule_generator/rrule_generator.dart';
|
||||
import 'package:time_range_picker/time_range_picker.dart';
|
||||
|
||||
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
||||
import '../../../../extensions/dateTime.dart';
|
||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||
import '../../../../widget/focusBehaviour.dart';
|
||||
import '../../../../widget/infoDialog.dart';
|
||||
import 'custom_event_colors.dart';
|
||||
|
||||
class CustomEventEditDialog extends StatefulWidget {
|
||||
final CustomTimetableEvent? existingEvent;
|
||||
|
||||
const CustomEventEditDialog({this.existingEvent, super.key});
|
||||
|
||||
@override
|
||||
State<CustomEventEditDialog> createState() => _CustomEventEditDialogState();
|
||||
}
|
||||
|
||||
class _CustomEventEditDialogState extends State<CustomEventEditDialog> {
|
||||
late DateTime _date = widget.existingEvent?.startDate ?? DateTime.now();
|
||||
late TimeOfDay _startTime = widget.existingEvent?.startDate.toTimeOfDay() ?? const TimeOfDay(hour: 8, minute: 0);
|
||||
late TimeOfDay _endTime = widget.existingEvent?.endDate.toTimeOfDay() ?? const TimeOfDay(hour: 9, minute: 30);
|
||||
late final TextEditingController _name = TextEditingController(text: widget.existingEvent?.title);
|
||||
late final TextEditingController _description = TextEditingController(text: widget.existingEvent?.description);
|
||||
late String _rrule = widget.existingEvent?.rrule ?? '';
|
||||
late CustomTimetableColors _color = CustomTimetableColors.values.firstWhere(
|
||||
(e) => e.name == widget.existingEvent?.color,
|
||||
orElse: () => TimetableColors.defaultColor,
|
||||
);
|
||||
|
||||
bool get _isEditing => widget.existingEvent != null;
|
||||
|
||||
bool _validate() => _name.text.isNotEmpty;
|
||||
|
||||
void _save() {
|
||||
if (!_validate()) return;
|
||||
|
||||
final edited = CustomTimetableEvent(
|
||||
id: widget.existingEvent?.id ?? '',
|
||||
title: _name.text,
|
||||
description: _description.text,
|
||||
startDate: _date.withTime(_startTime),
|
||||
endDate: _date.withTime(_endTime),
|
||||
color: _color.name,
|
||||
rrule: _rrule,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
final bloc = context.read<TimetableBloc>();
|
||||
final future = _isEditing
|
||||
? bloc.updateCustomEvent(widget.existingEvent!.id, edited)
|
||||
: bloc.addCustomEvent(edited);
|
||||
|
||||
future.then((_) {
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
}).catchError((Object error) {
|
||||
if (!mounted) return;
|
||||
InfoDialog.show(context, error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pickDate() async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _date,
|
||||
firstDate: DateTime.now().subtract(const Duration(days: 30)),
|
||||
lastDate: DateTime.now().add(const Duration(days: 30)),
|
||||
);
|
||||
if (picked != null && picked != _date) setState(() => _date = picked);
|
||||
}
|
||||
|
||||
Future<void> _pickTimeRange() async {
|
||||
final range = await showTimeRangePicker(
|
||||
context: context,
|
||||
start: _startTime,
|
||||
end: _endTime,
|
||||
disabledTime: TimeRange(
|
||||
startTime: const TimeOfDay(hour: 16, minute: 30),
|
||||
endTime: const TimeOfDay(hour: 8, minute: 0),
|
||||
),
|
||||
disabledColor: Colors.grey,
|
||||
paintingStyle: PaintingStyle.fill,
|
||||
interval: const Duration(minutes: 5),
|
||||
fromText: 'Beginnend',
|
||||
toText: 'Endend',
|
||||
strokeColor: Theme.of(context).colorScheme.secondary,
|
||||
minDuration: const Duration(minutes: 15),
|
||||
selectedColor: Theme.of(context).primaryColor,
|
||||
ticks: 24,
|
||||
);
|
||||
setState(() {
|
||||
_startTime = range.startTime;
|
||||
_endTime = range.endTime;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AlertDialog(
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
title: Text(_isEditing ? 'Termin bearbeiten' : 'Termin hinzufügen'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: TextField(
|
||||
controller: _name,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(labelText: 'Terminname', border: OutlineInputBorder()),
|
||||
onTapOutside: (_) => FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: TextField(
|
||||
controller: _description,
|
||||
maxLines: 2,
|
||||
minLines: 2,
|
||||
decoration: const InputDecoration(labelText: 'Beschreibung', border: OutlineInputBorder()),
|
||||
onTapOutside: (_) => FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.date_range_outlined),
|
||||
title: Text(Jiffy.parseFromDateTime(_date).yMMMd),
|
||||
subtitle: const Text('Datum'),
|
||||
onTap: _pickDate,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.access_time_outlined),
|
||||
title: Text('${_startTime.format(context)} - ${_endTime.format(context)}'),
|
||||
subtitle: const Text('Zeitraum'),
|
||||
onTap: _pickTimeRange,
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.color_lens_outlined),
|
||||
title: const Text('Farbgebung'),
|
||||
trailing: DropdownButton<CustomTimetableColors>(
|
||||
value: _color,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
items: CustomTimetableColors.values
|
||||
.map((e) => DropdownMenuItem<CustomTimetableColors>(
|
||||
value: e,
|
||||
enabled: e != _color,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.circle, color: TimetableColors.getDisplayOptions(e).color),
|
||||
const SizedBox(width: 10),
|
||||
Text(TimetableColors.getDisplayOptions(e).displayName),
|
||||
],
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (e) => setState(() => _color = e!),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
RRuleGenerator(
|
||||
config: RRuleGeneratorConfig(
|
||||
headerEnabled: true,
|
||||
weekdayBackgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
weekdaySelectedBackgroundColor: Theme.of(context).primaryColor,
|
||||
weekdayColor: Colors.black,
|
||||
),
|
||||
initialRRule: _rrule,
|
||||
textDelegate: const GermanRRuleTextDelegate(),
|
||||
onChange: (newValue) {
|
||||
log('Rule: $newValue');
|
||||
setState(() => _rrule = newValue);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Abbrechen')),
|
||||
TextButton(onPressed: _save, child: Text(_isEditing ? 'Speichern' : 'Erstellen')),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
|
||||
import '../../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
||||
import '../../../../widget/centeredLeading.dart';
|
||||
import '../../../../widget/placeholderView.dart';
|
||||
import '../details/delete_custom_event.dart';
|
||||
import 'custom_event_edit_dialog.dart';
|
||||
|
||||
class CustomEventsView extends StatelessWidget {
|
||||
const CustomEventsView({super.key});
|
||||
|
||||
void _openCreateDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => const CustomEventEditDialog(),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Eigene Termine'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => _openCreateDialog(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: LoadableStateConsumer<TimetableBloc, TimetableState>(
|
||||
child: (state, _) {
|
||||
final events = state.customEvents?.events ?? const [];
|
||||
|
||||
if (events.isEmpty) {
|
||||
return PlaceholderView(
|
||||
icon: Icons.calendar_today_outlined,
|
||||
text: 'Keine Einträge vorhanden',
|
||||
button: TextButton(
|
||||
onPressed: () => _openCreateDialog(context),
|
||||
child: const Text('Termin erstellen'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView(
|
||||
children: events.map((e) => ListTile(
|
||||
title: Text(e.title),
|
||||
subtitle: Text(
|
||||
'${e.rrule.isNotEmpty ? "wiederholend, " : ""}'
|
||||
'beginnend ${Jiffy.parseFromDateTime(e.startDate).fromNow()}',
|
||||
),
|
||||
leading: CenteredLeading(Icon(e.rrule.isEmpty ? Icons.event_outlined : Icons.event_repeat_outlined)),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => CustomEventEditDialog(existingEvent: e),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
onPressed: () => showDeleteCustomEventDialog(context, e),
|
||||
),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user