feat(02-03): create task providers, form screen with frequency and effort selectors
- TaskActions AsyncNotifier for create, update, delete, complete task mutations - tasksInRoomProvider manual StreamProvider.family wrapping TasksDao.watchTasksInRoom - TaskFormScreen with name, frequency (10 presets + custom), effort (3-way segmented), description, and initial due date picker (German DD.MM.YYYY format) - Custom frequency: number + unit picker (Tage/Wochen/Monate) - Calendar-anchored intervals auto-set anchorDay from due date - Edit mode loads existing task and pre-fills all fields - 19 new German localization keys for task form, delete, and empty state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,442 @@
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// Placeholder for the task creation/edit form.
|
||||
/// Will be fully implemented in Plan 03.
|
||||
class TaskFormScreen extends StatelessWidget {
|
||||
const TaskFormScreen({super.key, this.roomId, this.taskId});
|
||||
import '../../../core/database/database.dart';
|
||||
import '../../../core/providers/database_provider.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../domain/effort_level.dart';
|
||||
import '../domain/frequency.dart';
|
||||
import 'task_providers.dart';
|
||||
|
||||
/// Full-screen form for task creation and editing.
|
||||
///
|
||||
/// Pass [roomId] for create mode, or [taskId] for edit mode.
|
||||
class TaskFormScreen extends ConsumerStatefulWidget {
|
||||
final int? roomId;
|
||||
final int? taskId;
|
||||
|
||||
const TaskFormScreen({super.key, this.roomId, this.taskId});
|
||||
|
||||
bool get isEditing => taskId != null;
|
||||
|
||||
@override
|
||||
ConsumerState<TaskFormScreen> createState() => _TaskFormScreenState();
|
||||
}
|
||||
|
||||
class _TaskFormScreenState extends ConsumerState<TaskFormScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _nameController = TextEditingController();
|
||||
final _descriptionController = TextEditingController();
|
||||
final _customIntervalController = TextEditingController(text: '2');
|
||||
|
||||
FrequencyInterval? _selectedPreset;
|
||||
bool _isCustomFrequency = false;
|
||||
_CustomUnit _customUnit = _CustomUnit.days;
|
||||
EffortLevel _effortLevel = EffortLevel.medium;
|
||||
DateTime _dueDate = DateTime.now();
|
||||
Task? _existingTask;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedPreset = FrequencyInterval.presets[3]; // Default: weekly
|
||||
_dueDate = _dateOnly(DateTime.now());
|
||||
if (widget.isEditing) {
|
||||
_loadExistingTask();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadExistingTask() async {
|
||||
final db = ref.read(appDatabaseProvider);
|
||||
final task = await (db.select(db.tasks)
|
||||
..where((t) => t.id.equals(widget.taskId!)))
|
||||
.getSingle();
|
||||
|
||||
setState(() {
|
||||
_existingTask = task;
|
||||
_nameController.text = task.name;
|
||||
_descriptionController.text = task.description ?? '';
|
||||
_effortLevel = task.effortLevel;
|
||||
_dueDate = task.nextDueDate;
|
||||
|
||||
// Find matching preset
|
||||
_selectedPreset = null;
|
||||
_isCustomFrequency = true;
|
||||
for (final preset in FrequencyInterval.presets) {
|
||||
if (preset.intervalType == task.intervalType &&
|
||||
preset.days == task.intervalDays) {
|
||||
_selectedPreset = preset;
|
||||
_isCustomFrequency = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isCustomFrequency) {
|
||||
// Determine custom unit from stored interval
|
||||
switch (task.intervalType) {
|
||||
case IntervalType.everyNMonths:
|
||||
_customUnit = _CustomUnit.months;
|
||||
_customIntervalController.text = task.intervalDays.toString();
|
||||
case IntervalType.monthly:
|
||||
_customUnit = _CustomUnit.months;
|
||||
_customIntervalController.text = '1';
|
||||
case IntervalType.weekly:
|
||||
_customUnit = _CustomUnit.weeks;
|
||||
_customIntervalController.text = '1';
|
||||
case IntervalType.biweekly:
|
||||
_customUnit = _CustomUnit.weeks;
|
||||
_customIntervalController.text = '2';
|
||||
default:
|
||||
_customUnit = _CustomUnit.days;
|
||||
_customIntervalController.text = task.intervalDays.toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_descriptionController.dispose();
|
||||
_customIntervalController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int get _roomId => widget.roomId ?? _existingTask!.roomId;
|
||||
|
||||
static DateTime _dateOnly(DateTime dt) => DateTime(dt.year, dt.month, dt.day);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Aufgabe')),
|
||||
body: const Center(child: Text('Demnächst verfügbar')),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
widget.isEditing ? l10n.taskFormEditTitle : l10n.taskFormCreateTitle,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _isLoading ? null : _onSave,
|
||||
icon: const Icon(Icons.check),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// Name field (required, autofocus)
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
autofocus: !widget.isEditing,
|
||||
maxLength: 200,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.taskFormNameLabel,
|
||||
hintText: l10n.taskFormNameHint,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return l10n.taskFormNameRequired;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Frequency selector
|
||||
Text(
|
||||
l10n.taskFormFrequencyLabel,
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildFrequencySelector(l10n, theme),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Effort selector
|
||||
Text(
|
||||
l10n.taskFormEffortLabel,
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildEffortSelector(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Description (optional)
|
||||
TextFormField(
|
||||
controller: _descriptionController,
|
||||
maxLines: 3,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.taskFormDescriptionLabel,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Initial due date
|
||||
Text(
|
||||
l10n.taskFormDueDateLabel,
|
||||
style: theme.textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDueDatePicker(theme),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFrequencySelector(AppLocalizations l10n, ThemeData theme) {
|
||||
final items = <Widget>[];
|
||||
|
||||
// Preset intervals
|
||||
for (final preset in FrequencyInterval.presets) {
|
||||
items.add(
|
||||
ChoiceChip(
|
||||
label: Text(preset.label()),
|
||||
selected: !_isCustomFrequency && _selectedPreset == preset,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
setState(() {
|
||||
_selectedPreset = preset;
|
||||
_isCustomFrequency = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Custom option
|
||||
items.add(
|
||||
ChoiceChip(
|
||||
label: Text(l10n.taskFormFrequencyCustom),
|
||||
selected: _isCustomFrequency,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
setState(() {
|
||||
_isCustomFrequency = true;
|
||||
_selectedPreset = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 4,
|
||||
children: items,
|
||||
),
|
||||
if (_isCustomFrequency) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildCustomFrequencyInput(l10n, theme),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCustomFrequencyInput(AppLocalizations l10n, ThemeData theme) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.taskFormFrequencyEvery,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: TextFormField(
|
||||
controller: _customIntervalController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
textAlign: TextAlign.center,
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: SegmentedButton<_CustomUnit>(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: _CustomUnit.days,
|
||||
label: Text(l10n.taskFormFrequencyUnitDays),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: _CustomUnit.weeks,
|
||||
label: Text(l10n.taskFormFrequencyUnitWeeks),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: _CustomUnit.months,
|
||||
label: Text(l10n.taskFormFrequencyUnitMonths),
|
||||
),
|
||||
],
|
||||
selected: {_customUnit},
|
||||
onSelectionChanged: (newSelection) {
|
||||
setState(() {
|
||||
_customUnit = newSelection.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEffortSelector() {
|
||||
return SegmentedButton<EffortLevel>(
|
||||
segments: EffortLevel.values
|
||||
.map((e) => ButtonSegment(
|
||||
value: e,
|
||||
label: Text(e.label()),
|
||||
))
|
||||
.toList(),
|
||||
selected: {_effortLevel},
|
||||
onSelectionChanged: (newSelection) {
|
||||
setState(() {
|
||||
_effortLevel = newSelection.first;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDueDatePicker(ThemeData theme) {
|
||||
final dateFormat = DateFormat('dd.MM.yyyy');
|
||||
return InkWell(
|
||||
onTap: _pickDueDate,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: InputDecorator(
|
||||
decoration: const InputDecoration(
|
||||
suffixIcon: Icon(Icons.calendar_today),
|
||||
),
|
||||
child: Text(
|
||||
dateFormat.format(_dueDate),
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickDueDate() async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _dueDate,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2100),
|
||||
locale: const Locale('de'),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_dueDate = _dateOnly(picked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the frequency from either selected preset or custom input.
|
||||
({IntervalType type, int days, int? anchorDay}) _resolveFrequency() {
|
||||
if (!_isCustomFrequency && _selectedPreset != null) {
|
||||
final preset = _selectedPreset!;
|
||||
// For calendar-anchored intervals, set anchorDay to due date's day
|
||||
int? anchorDay;
|
||||
if (_isCalendarAnchored(preset.intervalType)) {
|
||||
anchorDay = _dueDate.day;
|
||||
}
|
||||
return (
|
||||
type: preset.intervalType,
|
||||
days: preset.days,
|
||||
anchorDay: anchorDay,
|
||||
);
|
||||
}
|
||||
|
||||
// Custom frequency
|
||||
final number = int.tryParse(_customIntervalController.text) ?? 1;
|
||||
switch (_customUnit) {
|
||||
case _CustomUnit.days:
|
||||
return (
|
||||
type: IntervalType.everyNDays,
|
||||
days: number,
|
||||
anchorDay: null,
|
||||
);
|
||||
case _CustomUnit.weeks:
|
||||
return (
|
||||
type: IntervalType.everyNDays,
|
||||
days: number * 7,
|
||||
anchorDay: null,
|
||||
);
|
||||
case _CustomUnit.months:
|
||||
return (
|
||||
type: IntervalType.everyNMonths,
|
||||
days: number,
|
||||
anchorDay: _dueDate.day,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isCalendarAnchored(IntervalType type) {
|
||||
return type == IntervalType.monthly ||
|
||||
type == IntervalType.everyNMonths ||
|
||||
type == IntervalType.quarterly ||
|
||||
type == IntervalType.yearly;
|
||||
}
|
||||
|
||||
Future<void> _onSave() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final freq = _resolveFrequency();
|
||||
final actions = ref.read(taskActionsProvider.notifier);
|
||||
|
||||
if (widget.isEditing && _existingTask != null) {
|
||||
final updatedTask = _existingTask!.copyWith(
|
||||
name: _nameController.text.trim(),
|
||||
description: Value(_descriptionController.text.trim().isEmpty
|
||||
? null
|
||||
: _descriptionController.text.trim()),
|
||||
intervalType: freq.type,
|
||||
intervalDays: freq.days,
|
||||
anchorDay: Value(freq.anchorDay),
|
||||
effortLevel: _effortLevel,
|
||||
nextDueDate: _dueDate,
|
||||
);
|
||||
await actions.updateTask(updatedTask);
|
||||
} else {
|
||||
await actions.createTask(
|
||||
roomId: _roomId,
|
||||
name: _nameController.text.trim(),
|
||||
description: _descriptionController.text.trim().isEmpty
|
||||
? null
|
||||
: _descriptionController.text.trim(),
|
||||
intervalType: freq.type,
|
||||
intervalDays: freq.days,
|
||||
anchorDay: freq.anchorDay,
|
||||
effortLevel: _effortLevel,
|
||||
nextDueDate: _dueDate,
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
context.pop();
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unit options for custom frequency input.
|
||||
enum _CustomUnit { days, weeks, months }
|
||||
|
||||
65
lib/features/tasks/presentation/task_providers.dart
Normal file
65
lib/features/tasks/presentation/task_providers.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import 'package:household_keeper/core/database/database.dart';
|
||||
import 'package:household_keeper/core/providers/database_provider.dart';
|
||||
import 'package:household_keeper/features/tasks/domain/effort_level.dart';
|
||||
import 'package:household_keeper/features/tasks/domain/frequency.dart';
|
||||
|
||||
part 'task_providers.g.dart';
|
||||
|
||||
/// Stream provider family for tasks in a specific room, sorted by due date.
|
||||
///
|
||||
/// Defined manually because riverpod_generator has trouble with drift's
|
||||
/// generated [Task] type in family provider return types.
|
||||
final tasksInRoomProvider =
|
||||
StreamProvider.family.autoDispose<List<Task>, int>((ref, roomId) {
|
||||
final db = ref.watch(appDatabaseProvider);
|
||||
return db.tasksDao.watchTasksInRoom(roomId);
|
||||
});
|
||||
|
||||
/// Notifier for task mutations: create, update, delete, complete.
|
||||
@riverpod
|
||||
class TaskActions extends _$TaskActions {
|
||||
@override
|
||||
FutureOr<void> build() {}
|
||||
|
||||
Future<int> createTask({
|
||||
required int roomId,
|
||||
required String name,
|
||||
String? description,
|
||||
required IntervalType intervalType,
|
||||
required int intervalDays,
|
||||
int? anchorDay,
|
||||
required EffortLevel effortLevel,
|
||||
required DateTime nextDueDate,
|
||||
}) async {
|
||||
final db = ref.read(appDatabaseProvider);
|
||||
return db.tasksDao.insertTask(TasksCompanion.insert(
|
||||
roomId: roomId,
|
||||
name: name,
|
||||
description: Value(description),
|
||||
intervalType: intervalType,
|
||||
intervalDays: Value(intervalDays),
|
||||
anchorDay: Value(anchorDay),
|
||||
effortLevel: effortLevel,
|
||||
nextDueDate: nextDueDate,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> updateTask(Task task) async {
|
||||
final db = ref.read(appDatabaseProvider);
|
||||
await db.tasksDao.updateTask(task);
|
||||
}
|
||||
|
||||
Future<void> deleteTask(int taskId) async {
|
||||
final db = ref.read(appDatabaseProvider);
|
||||
await db.tasksDao.deleteTask(taskId);
|
||||
}
|
||||
|
||||
Future<void> completeTask(int taskId) async {
|
||||
final db = ref.read(appDatabaseProvider);
|
||||
await db.tasksDao.completeTask(taskId);
|
||||
}
|
||||
}
|
||||
59
lib/features/tasks/presentation/task_providers.g.dart
Normal file
59
lib/features/tasks/presentation/task_providers.g.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'task_providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
/// Notifier for task mutations: create, update, delete, complete.
|
||||
|
||||
@ProviderFor(TaskActions)
|
||||
final taskActionsProvider = TaskActionsProvider._();
|
||||
|
||||
/// Notifier for task mutations: create, update, delete, complete.
|
||||
final class TaskActionsProvider
|
||||
extends $AsyncNotifierProvider<TaskActions, void> {
|
||||
/// Notifier for task mutations: create, update, delete, complete.
|
||||
TaskActionsProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'taskActionsProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$taskActionsHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
TaskActions create() => TaskActions();
|
||||
}
|
||||
|
||||
String _$taskActionsHash() => r'62f1739263e3cfb379b83de10d712b17fd087f92';
|
||||
|
||||
/// Notifier for task mutations: create, update, delete, complete.
|
||||
|
||||
abstract class _$TaskActions extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref = this.ref as $Ref<AsyncValue<void>, void>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AsyncValue<void>, void>,
|
||||
AsyncValue<void>,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(ref, build);
|
||||
}
|
||||
}
|
||||
@@ -39,5 +39,25 @@
|
||||
"count": { "type": "int" }
|
||||
}
|
||||
},
|
||||
"cancel": "Abbrechen"
|
||||
"cancel": "Abbrechen",
|
||||
"taskFormCreateTitle": "Aufgabe erstellen",
|
||||
"taskFormEditTitle": "Aufgabe bearbeiten",
|
||||
"taskFormNameLabel": "Aufgabenname",
|
||||
"taskFormNameHint": "z.B. Staubsaugen, Fenster putzen...",
|
||||
"taskFormNameRequired": "Bitte einen Namen eingeben",
|
||||
"taskFormFrequencyLabel": "Wiederholung",
|
||||
"taskFormFrequencyCustom": "Benutzerdefiniert",
|
||||
"taskFormFrequencyEvery": "Alle",
|
||||
"taskFormFrequencyUnitDays": "Tage",
|
||||
"taskFormFrequencyUnitWeeks": "Wochen",
|
||||
"taskFormFrequencyUnitMonths": "Monate",
|
||||
"taskFormEffortLabel": "Aufwand",
|
||||
"taskFormDescriptionLabel": "Beschreibung (optional)",
|
||||
"taskFormDueDateLabel": "Erstes F\u00e4lligkeitsdatum",
|
||||
"taskDeleteConfirmTitle": "Aufgabe l\u00f6schen?",
|
||||
"taskDeleteConfirmMessage": "Die Aufgabe wird unwiderruflich gel\u00f6scht.",
|
||||
"taskDeleteConfirmAction": "L\u00f6schen",
|
||||
"taskEmptyTitle": "Noch keine Aufgaben",
|
||||
"taskEmptyMessage": "Erstelle die erste Aufgabe f\u00fcr diesen Raum.",
|
||||
"taskEmptyAction": "Aufgabe erstellen"
|
||||
}
|
||||
|
||||
@@ -273,6 +273,126 @@ abstract class AppLocalizations {
|
||||
/// In de, this message translates to:
|
||||
/// **'Abbrechen'**
|
||||
String get cancel;
|
||||
|
||||
/// No description provided for @taskFormCreateTitle.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufgabe erstellen'**
|
||||
String get taskFormCreateTitle;
|
||||
|
||||
/// No description provided for @taskFormEditTitle.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufgabe bearbeiten'**
|
||||
String get taskFormEditTitle;
|
||||
|
||||
/// No description provided for @taskFormNameLabel.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufgabenname'**
|
||||
String get taskFormNameLabel;
|
||||
|
||||
/// No description provided for @taskFormNameHint.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'z.B. Staubsaugen, Fenster putzen...'**
|
||||
String get taskFormNameHint;
|
||||
|
||||
/// No description provided for @taskFormNameRequired.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Bitte einen Namen eingeben'**
|
||||
String get taskFormNameRequired;
|
||||
|
||||
/// No description provided for @taskFormFrequencyLabel.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Wiederholung'**
|
||||
String get taskFormFrequencyLabel;
|
||||
|
||||
/// No description provided for @taskFormFrequencyCustom.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Benutzerdefiniert'**
|
||||
String get taskFormFrequencyCustom;
|
||||
|
||||
/// No description provided for @taskFormFrequencyEvery.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Alle'**
|
||||
String get taskFormFrequencyEvery;
|
||||
|
||||
/// No description provided for @taskFormFrequencyUnitDays.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Tage'**
|
||||
String get taskFormFrequencyUnitDays;
|
||||
|
||||
/// No description provided for @taskFormFrequencyUnitWeeks.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Wochen'**
|
||||
String get taskFormFrequencyUnitWeeks;
|
||||
|
||||
/// No description provided for @taskFormFrequencyUnitMonths.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Monate'**
|
||||
String get taskFormFrequencyUnitMonths;
|
||||
|
||||
/// No description provided for @taskFormEffortLabel.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufwand'**
|
||||
String get taskFormEffortLabel;
|
||||
|
||||
/// No description provided for @taskFormDescriptionLabel.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Beschreibung (optional)'**
|
||||
String get taskFormDescriptionLabel;
|
||||
|
||||
/// No description provided for @taskFormDueDateLabel.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Erstes Fälligkeitsdatum'**
|
||||
String get taskFormDueDateLabel;
|
||||
|
||||
/// No description provided for @taskDeleteConfirmTitle.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufgabe löschen?'**
|
||||
String get taskDeleteConfirmTitle;
|
||||
|
||||
/// No description provided for @taskDeleteConfirmMessage.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Die Aufgabe wird unwiderruflich gelöscht.'**
|
||||
String get taskDeleteConfirmMessage;
|
||||
|
||||
/// No description provided for @taskDeleteConfirmAction.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Löschen'**
|
||||
String get taskDeleteConfirmAction;
|
||||
|
||||
/// No description provided for @taskEmptyTitle.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Noch keine Aufgaben'**
|
||||
String get taskEmptyTitle;
|
||||
|
||||
/// No description provided for @taskEmptyMessage.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Erstelle die erste Aufgabe für diesen Raum.'**
|
||||
String get taskEmptyMessage;
|
||||
|
||||
/// No description provided for @taskEmptyAction.
|
||||
///
|
||||
/// In de, this message translates to:
|
||||
/// **'Aufgabe erstellen'**
|
||||
String get taskEmptyAction;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -103,4 +103,65 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get cancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get taskFormCreateTitle => 'Aufgabe erstellen';
|
||||
|
||||
@override
|
||||
String get taskFormEditTitle => 'Aufgabe bearbeiten';
|
||||
|
||||
@override
|
||||
String get taskFormNameLabel => 'Aufgabenname';
|
||||
|
||||
@override
|
||||
String get taskFormNameHint => 'z.B. Staubsaugen, Fenster putzen...';
|
||||
|
||||
@override
|
||||
String get taskFormNameRequired => 'Bitte einen Namen eingeben';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyLabel => 'Wiederholung';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyCustom => 'Benutzerdefiniert';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyEvery => 'Alle';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyUnitDays => 'Tage';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyUnitWeeks => 'Wochen';
|
||||
|
||||
@override
|
||||
String get taskFormFrequencyUnitMonths => 'Monate';
|
||||
|
||||
@override
|
||||
String get taskFormEffortLabel => 'Aufwand';
|
||||
|
||||
@override
|
||||
String get taskFormDescriptionLabel => 'Beschreibung (optional)';
|
||||
|
||||
@override
|
||||
String get taskFormDueDateLabel => 'Erstes Fälligkeitsdatum';
|
||||
|
||||
@override
|
||||
String get taskDeleteConfirmTitle => 'Aufgabe löschen?';
|
||||
|
||||
@override
|
||||
String get taskDeleteConfirmMessage =>
|
||||
'Die Aufgabe wird unwiderruflich gelöscht.';
|
||||
|
||||
@override
|
||||
String get taskDeleteConfirmAction => 'Löschen';
|
||||
|
||||
@override
|
||||
String get taskEmptyTitle => 'Noch keine Aufgaben';
|
||||
|
||||
@override
|
||||
String get taskEmptyMessage => 'Erstelle die erste Aufgabe für diesen Raum.';
|
||||
|
||||
@override
|
||||
String get taskEmptyAction => 'Aufgabe erstellen';
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ void main() {
|
||||
overrides: [
|
||||
// Override the stream provider to return an empty list immediately
|
||||
// so that the rooms screen shows the empty state without needing a DB.
|
||||
// ignore: scoped_providers_should_specify_dependencies
|
||||
roomWithStatsListProvider.overrideWith(
|
||||
(ref) => Stream.value([]),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user