feat(02-02): create room providers, form screen, icon picker, and router routes
- Add Riverpod providers (roomWithStatsList, RoomActions) connecting to RoomsDao - Create RoomFormScreen with name field, icon picker preview, create/edit modes - Create IconPickerSheet bottom sheet with curated Material Icons grid - Add nested GoRouter routes: /rooms/new, /rooms/:roomId, /rooms/:roomId/edit - Add placeholder TaskListScreen and TaskFormScreen for Plan 03 routes - Add 11 new German localization keys for room management UI - Add flutter_reorderable_grid_view dependency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'package:household_keeper/features/home/presentation/home_screen.dart';
|
import 'package:household_keeper/features/home/presentation/home_screen.dart';
|
||||||
|
import 'package:household_keeper/features/rooms/presentation/room_form_screen.dart';
|
||||||
import 'package:household_keeper/features/rooms/presentation/rooms_screen.dart';
|
import 'package:household_keeper/features/rooms/presentation/rooms_screen.dart';
|
||||||
import 'package:household_keeper/features/settings/presentation/settings_screen.dart';
|
import 'package:household_keeper/features/settings/presentation/settings_screen.dart';
|
||||||
|
import 'package:household_keeper/features/tasks/presentation/task_form_screen.dart';
|
||||||
|
import 'package:household_keeper/features/tasks/presentation/task_list_screen.dart';
|
||||||
import 'package:household_keeper/shell/app_shell.dart';
|
import 'package:household_keeper/shell/app_shell.dart';
|
||||||
|
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
@@ -25,6 +28,46 @@ final router = GoRouter(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/rooms',
|
path: '/rooms',
|
||||||
builder: (context, state) => const RoomsScreen(),
|
builder: (context, state) => const RoomsScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'new',
|
||||||
|
builder: (context, state) => const RoomFormScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: ':roomId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final roomId =
|
||||||
|
int.parse(state.pathParameters['roomId']!);
|
||||||
|
return TaskListScreen(roomId: roomId);
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final roomId =
|
||||||
|
int.parse(state.pathParameters['roomId']!);
|
||||||
|
return RoomFormScreen(roomId: roomId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'tasks/new',
|
||||||
|
builder: (context, state) {
|
||||||
|
final roomId =
|
||||||
|
int.parse(state.pathParameters['roomId']!);
|
||||||
|
return TaskFormScreen(roomId: roomId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'tasks/:taskId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final taskId =
|
||||||
|
int.parse(state.pathParameters['taskId']!);
|
||||||
|
return TaskFormScreen(taskId: taskId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
94
lib/features/rooms/presentation/icon_picker_sheet.dart
Normal file
94
lib/features/rooms/presentation/icon_picker_sheet.dart
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:household_keeper/features/rooms/domain/room_icons.dart';
|
||||||
|
|
||||||
|
/// Shows a modal bottom sheet with a grid of curated Material Icons.
|
||||||
|
///
|
||||||
|
/// Returns the selected icon name, or null if dismissed.
|
||||||
|
Future<String?> showIconPickerSheet({
|
||||||
|
required BuildContext context,
|
||||||
|
String? selectedIconName,
|
||||||
|
}) {
|
||||||
|
return showModalBottomSheet<String>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => IconPickerSheet(
|
||||||
|
selectedIconName: selectedIconName,
|
||||||
|
onIconSelected: (name) => Navigator.of(context).pop(name),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grid of curated household Material Icons for room icon selection.
|
||||||
|
class IconPickerSheet extends StatelessWidget {
|
||||||
|
const IconPickerSheet({
|
||||||
|
super.key,
|
||||||
|
this.selectedIconName,
|
||||||
|
required this.onIconSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? selectedIconName;
|
||||||
|
final ValueChanged<String> onIconSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Drag handle
|
||||||
|
Container(
|
||||||
|
width: 32,
|
||||||
|
height: 4,
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Symbol w\u00e4hlen',
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GridView.count(
|
||||||
|
crossAxisCount: 5,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
children: curatedRoomIcons.map((entry) {
|
||||||
|
final isSelected = entry.name == selectedIconName;
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => onIconSelected(entry.name),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? colorScheme.primaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
entry.icon,
|
||||||
|
size: 28,
|
||||||
|
color: isSelected
|
||||||
|
? colorScheme.onPrimaryContainer
|
||||||
|
: colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
207
lib/features/rooms/presentation/room_form_screen.dart
Normal file
207
lib/features/rooms/presentation/room_form_screen.dart
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import 'package:household_keeper/core/providers/database_provider.dart';
|
||||||
|
import 'package:household_keeper/features/rooms/domain/room_icons.dart';
|
||||||
|
import 'package:household_keeper/features/rooms/presentation/icon_picker_sheet.dart';
|
||||||
|
import 'package:household_keeper/features/rooms/presentation/room_providers.dart';
|
||||||
|
import 'package:household_keeper/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Full-screen form for creating and editing rooms.
|
||||||
|
///
|
||||||
|
/// Pass [roomId] to edit an existing room, or leave null to create a new one.
|
||||||
|
class RoomFormScreen extends ConsumerStatefulWidget {
|
||||||
|
const RoomFormScreen({super.key, this.roomId});
|
||||||
|
|
||||||
|
final int? roomId;
|
||||||
|
|
||||||
|
bool get isEditing => roomId != null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<RoomFormScreen> createState() => _RoomFormScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoomFormScreenState extends ConsumerState<RoomFormScreen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _nameController = TextEditingController();
|
||||||
|
String _selectedIconName = curatedRoomIcons.first.name;
|
||||||
|
bool _isLoading = false;
|
||||||
|
bool _isSaving = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.isEditing) {
|
||||||
|
_loadRoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRoom() async {
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
try {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
final room = await db.roomsDao.getRoomById(widget.roomId!);
|
||||||
|
_nameController.text = room.name;
|
||||||
|
setState(() {
|
||||||
|
_selectedIconName = room.iconName;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _isLoading = false);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Fehler beim Laden: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _save() async {
|
||||||
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
setState(() => _isSaving = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final actions = ref.read(roomActionsProvider.notifier);
|
||||||
|
if (widget.isEditing) {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
final existing = await db.roomsDao.getRoomById(widget.roomId!);
|
||||||
|
await actions.updateRoom(existing.copyWith(
|
||||||
|
name: _nameController.text.trim(),
|
||||||
|
iconName: _selectedIconName,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
await actions.createRoom(
|
||||||
|
_nameController.text.trim(),
|
||||||
|
_selectedIconName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (mounted) context.pop();
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _isSaving = false);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Fehler beim Speichern: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
widget.isEditing ? l10n.roomFormEditTitle : l10n.roomFormCreateTitle,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _isSaving ? null : _save,
|
||||||
|
icon: _isSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.check),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
// Room name field
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
autofocus: !widget.isEditing,
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: l10n.roomFormNameLabel,
|
||||||
|
hintText: l10n.roomFormNameHint,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return l10n.roomFormNameRequired;
|
||||||
|
}
|
||||||
|
if (value.trim().length > 100) {
|
||||||
|
return 'Maximal 100 Zeichen';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Icon picker preview
|
||||||
|
Text(
|
||||||
|
l10n.roomFormIconLabel,
|
||||||
|
style: theme.textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final selected = await showIconPickerSheet(
|
||||||
|
context: context,
|
||||||
|
selectedIconName: _selectedIconName,
|
||||||
|
);
|
||||||
|
if (selected != null) {
|
||||||
|
setState(() => _selectedIconName = selected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.surfaceContainerLow,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: theme.colorScheme.outline.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
mapIconName(_selectedIconName),
|
||||||
|
size: 28,
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
l10n.roomFormIconLabel,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/features/rooms/presentation/room_providers.dart
Normal file
48
lib/features/rooms/presentation/room_providers.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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/rooms/data/rooms_dao.dart';
|
||||||
|
|
||||||
|
part 'room_providers.g.dart';
|
||||||
|
|
||||||
|
/// Watches all rooms with computed task stats (due count, cleanliness ratio).
|
||||||
|
@riverpod
|
||||||
|
Stream<List<RoomWithStats>> roomWithStatsList(Ref ref) {
|
||||||
|
final db = ref.watch(appDatabaseProvider);
|
||||||
|
return db.roomsDao.watchRoomWithStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Async notifier for room mutation actions (create, update, delete, reorder).
|
||||||
|
@riverpod
|
||||||
|
class RoomActions extends _$RoomActions {
|
||||||
|
@override
|
||||||
|
FutureOr<void> build() {}
|
||||||
|
|
||||||
|
/// Create a new room. Returns the auto-generated id.
|
||||||
|
Future<int> createRoom(String name, String iconName) async {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
return db.roomsDao.insertRoom(RoomsCompanion.insert(
|
||||||
|
name: name,
|
||||||
|
iconName: iconName,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update an existing room.
|
||||||
|
Future<void> updateRoom(Room room) async {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
await db.roomsDao.updateRoom(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a room and cascade to its tasks and completions.
|
||||||
|
Future<void> deleteRoom(int roomId) async {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
await db.roomsDao.deleteRoom(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reorder rooms by their IDs in the new order.
|
||||||
|
Future<void> reorderRooms(List<int> roomIds) async {
|
||||||
|
final db = ref.read(appDatabaseProvider);
|
||||||
|
await db.roomsDao.reorderRooms(roomIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
lib/features/rooms/presentation/room_providers.g.dart
Normal file
105
lib/features/rooms/presentation/room_providers.g.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'room_providers.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
/// Watches all rooms with computed task stats (due count, cleanliness ratio).
|
||||||
|
|
||||||
|
@ProviderFor(roomWithStatsList)
|
||||||
|
final roomWithStatsListProvider = RoomWithStatsListProvider._();
|
||||||
|
|
||||||
|
/// Watches all rooms with computed task stats (due count, cleanliness ratio).
|
||||||
|
|
||||||
|
final class RoomWithStatsListProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<RoomWithStats>>,
|
||||||
|
List<RoomWithStats>,
|
||||||
|
Stream<List<RoomWithStats>>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<List<RoomWithStats>>,
|
||||||
|
$StreamProvider<List<RoomWithStats>> {
|
||||||
|
/// Watches all rooms with computed task stats (due count, cleanliness ratio).
|
||||||
|
RoomWithStatsListProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'roomWithStatsListProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$roomWithStatsListHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$StreamProviderElement<List<RoomWithStats>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $StreamProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<RoomWithStats>> create(Ref ref) {
|
||||||
|
return roomWithStatsList(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$roomWithStatsListHash() => r'8f6f8d6be77725c38be13e9420609638ec2868f9';
|
||||||
|
|
||||||
|
/// Async notifier for room mutation actions (create, update, delete, reorder).
|
||||||
|
|
||||||
|
@ProviderFor(RoomActions)
|
||||||
|
final roomActionsProvider = RoomActionsProvider._();
|
||||||
|
|
||||||
|
/// Async notifier for room mutation actions (create, update, delete, reorder).
|
||||||
|
final class RoomActionsProvider
|
||||||
|
extends $AsyncNotifierProvider<RoomActions, void> {
|
||||||
|
/// Async notifier for room mutation actions (create, update, delete, reorder).
|
||||||
|
RoomActionsProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'roomActionsProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$roomActionsHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
RoomActions create() => RoomActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$roomActionsHash() => r'4004a7a39474cc4ea1e89b8533edaa217ac543ce';
|
||||||
|
|
||||||
|
/// Async notifier for room mutation actions (create, update, delete, reorder).
|
||||||
|
|
||||||
|
abstract class _$RoomActions 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/features/tasks/presentation/task_form_screen.dart
Normal file
18
lib/features/tasks/presentation/task_form_screen.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.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});
|
||||||
|
|
||||||
|
final int? roomId;
|
||||||
|
final int? taskId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Aufgabe')),
|
||||||
|
body: const Center(child: Text('Demnächst verfügbar')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
lib/features/tasks/presentation/task_list_screen.dart
Normal file
17
lib/features/tasks/presentation/task_list_screen.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Placeholder for the task list screen within a room.
|
||||||
|
/// Will be fully implemented in Plan 03.
|
||||||
|
class TaskListScreen extends StatelessWidget {
|
||||||
|
const TaskListScreen({super.key, required this.roomId});
|
||||||
|
|
||||||
|
final int roomId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Aufgaben')),
|
||||||
|
body: const Center(child: Text('Demnächst verfügbar')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,5 +23,21 @@
|
|||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": { "type": "String" }
|
"version": { "type": "String" }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"roomFormCreateTitle": "Raum erstellen",
|
||||||
|
"roomFormEditTitle": "Raum bearbeiten",
|
||||||
|
"roomFormNameLabel": "Raumname",
|
||||||
|
"roomFormNameHint": "z.B. K\u00fcche, Badezimmer...",
|
||||||
|
"roomFormNameRequired": "Bitte einen Namen eingeben",
|
||||||
|
"roomFormIconLabel": "Symbol w\u00e4hlen",
|
||||||
|
"roomDeleteConfirmTitle": "Raum l\u00f6schen?",
|
||||||
|
"roomDeleteConfirmMessage": "Der Raum und alle zugeh\u00f6rigen Aufgaben werden unwiderruflich gel\u00f6scht.",
|
||||||
|
"roomDeleteConfirmAction": "L\u00f6schen",
|
||||||
|
"roomCardDueCount": "{count} f\u00e4llig",
|
||||||
|
"@roomCardDueCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cancel": "Abbrechen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,72 @@ abstract class AppLocalizations {
|
|||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'Version {version}'**
|
/// **'Version {version}'**
|
||||||
String aboutVersion(String version);
|
String aboutVersion(String version);
|
||||||
|
|
||||||
|
/// No description provided for @roomFormCreateTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Raum erstellen'**
|
||||||
|
String get roomFormCreateTitle;
|
||||||
|
|
||||||
|
/// No description provided for @roomFormEditTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Raum bearbeiten'**
|
||||||
|
String get roomFormEditTitle;
|
||||||
|
|
||||||
|
/// No description provided for @roomFormNameLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Raumname'**
|
||||||
|
String get roomFormNameLabel;
|
||||||
|
|
||||||
|
/// No description provided for @roomFormNameHint.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'z.B. Küche, Badezimmer...'**
|
||||||
|
String get roomFormNameHint;
|
||||||
|
|
||||||
|
/// No description provided for @roomFormNameRequired.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Bitte einen Namen eingeben'**
|
||||||
|
String get roomFormNameRequired;
|
||||||
|
|
||||||
|
/// No description provided for @roomFormIconLabel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Symbol wählen'**
|
||||||
|
String get roomFormIconLabel;
|
||||||
|
|
||||||
|
/// No description provided for @roomDeleteConfirmTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Raum löschen?'**
|
||||||
|
String get roomDeleteConfirmTitle;
|
||||||
|
|
||||||
|
/// No description provided for @roomDeleteConfirmMessage.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Der Raum und alle zugehörigen Aufgaben werden unwiderruflich gelöscht.'**
|
||||||
|
String get roomDeleteConfirmMessage;
|
||||||
|
|
||||||
|
/// No description provided for @roomDeleteConfirmAction.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Löschen'**
|
||||||
|
String get roomDeleteConfirmAction;
|
||||||
|
|
||||||
|
/// No description provided for @roomCardDueCount.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{count} fällig'**
|
||||||
|
String roomCardDueCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @cancel.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Abbrechen'**
|
||||||
|
String get cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -67,4 +67,40 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String aboutVersion(String version) {
|
String aboutVersion(String version) {
|
||||||
return 'Version $version';
|
return 'Version $version';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormCreateTitle => 'Raum erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormEditTitle => 'Raum bearbeiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormNameLabel => 'Raumname';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormNameHint => 'z.B. Küche, Badezimmer...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormNameRequired => 'Bitte einen Namen eingeben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomFormIconLabel => 'Symbol wählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomDeleteConfirmTitle => 'Raum löschen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomDeleteConfirmMessage =>
|
||||||
|
'Der Raum und alle zugehörigen Aufgaben werden unwiderruflich gelöscht.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get roomDeleteConfirmAction => 'Löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String roomCardDueCount(int count) {
|
||||||
|
return '$count fällig';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Abbrechen';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ dependencies:
|
|||||||
go_router: ^17.1.0
|
go_router: ^17.1.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
shared_preferences: ^2.5.4
|
shared_preferences: ^2.5.4
|
||||||
|
flutter_reorderable_grid_view: ^5.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user