feat(07-02): build SortDropdown widget and integrate into HomeScreen and TaskListScreen
- Create SortDropdown ConsumerWidget using PopupMenuButton<TaskSortOption> - Displays current sort label with sort icon in AppBar actions - Check mark shown on active option via Opacity widget - Add Scaffold with AppBar (title: Übersicht, actions: SortDropdown) to HomeScreen - Add SortDropdown before edit/delete IconButtons in TaskListScreen AppBar
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:household_keeper/features/home/presentation/calendar_day_list.dart';
|
import 'package:household_keeper/features/home/presentation/calendar_day_list.dart';
|
||||||
import 'package:household_keeper/features/home/presentation/calendar_providers.dart';
|
import 'package:household_keeper/features/home/presentation/calendar_providers.dart';
|
||||||
import 'package:household_keeper/features/home/presentation/calendar_strip.dart';
|
import 'package:household_keeper/features/home/presentation/calendar_strip.dart';
|
||||||
|
import 'package:household_keeper/features/tasks/presentation/sort_dropdown.dart';
|
||||||
import 'package:household_keeper/l10n/app_localizations.dart';
|
import 'package:household_keeper/l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// The app's primary screen: a horizontal calendar strip at the top with a
|
/// The app's primary screen: a horizontal calendar strip at the top with a
|
||||||
@@ -30,40 +31,46 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Stack(
|
return Scaffold(
|
||||||
children: [
|
appBar: AppBar(
|
||||||
Column(
|
title: Text(l10n.tabHome),
|
||||||
children: [
|
actions: const [SortDropdown()],
|
||||||
CalendarStrip(
|
),
|
||||||
controller: _stripController,
|
body: Stack(
|
||||||
onTodayVisibilityChanged: (visible) {
|
children: [
|
||||||
setState(() => _showTodayButton = !visible);
|
Column(
|
||||||
},
|
children: [
|
||||||
),
|
CalendarStrip(
|
||||||
const Expanded(child: CalendarDayList()),
|
controller: _stripController,
|
||||||
],
|
onTodayVisibilityChanged: (visible) {
|
||||||
),
|
setState(() => _showTodayButton = !visible);
|
||||||
if (_showTodayButton)
|
|
||||||
Positioned(
|
|
||||||
bottom: 16,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Center(
|
|
||||||
child: FloatingActionButton.extended(
|
|
||||||
onPressed: () {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final today = DateTime(now.year, now.month, now.day);
|
|
||||||
ref
|
|
||||||
.read(selectedDateProvider.notifier)
|
|
||||||
.selectDate(today);
|
|
||||||
_stripController.scrollToToday();
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.today),
|
),
|
||||||
label: Text(l10n.calendarTodayButton),
|
const Expanded(child: CalendarDayList()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_showTodayButton)
|
||||||
|
Positioned(
|
||||||
|
bottom: 16,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
ref
|
||||||
|
.read(selectedDateProvider.notifier)
|
||||||
|
.selectDate(today);
|
||||||
|
_stripController.scrollToToday();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.today),
|
||||||
|
label: Text(l10n.calendarTodayButton),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
lib/features/tasks/presentation/sort_dropdown.dart
Normal file
71
lib/features/tasks/presentation/sort_dropdown.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:household_keeper/features/tasks/domain/task_sort_option.dart';
|
||||||
|
import 'package:household_keeper/features/tasks/presentation/sort_preference_notifier.dart';
|
||||||
|
import 'package:household_keeper/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
/// A reusable sort dropdown widget for use in AppBar actions.
|
||||||
|
///
|
||||||
|
/// Displays the current sort option as a labelled button with a sort icon.
|
||||||
|
/// Tapping opens a popup menu with three options: A–Z, Intervall, Aufwand.
|
||||||
|
/// The active option is indicated with a visible check mark.
|
||||||
|
///
|
||||||
|
/// Reads sort state from [sortPreferenceProvider] and writes via
|
||||||
|
/// [SortPreferenceNotifier.setSortOption].
|
||||||
|
class SortDropdown extends ConsumerWidget {
|
||||||
|
const SortDropdown({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final current = ref.watch(sortPreferenceProvider);
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return PopupMenuButton<TaskSortOption>(
|
||||||
|
onSelected: (value) =>
|
||||||
|
ref.read(sortPreferenceProvider.notifier).setSortOption(value),
|
||||||
|
itemBuilder: (context) => TaskSortOption.values.map((option) {
|
||||||
|
final isSelected = option == current;
|
||||||
|
return PopupMenuItem<TaskSortOption>(
|
||||||
|
value: option,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Opacity(
|
||||||
|
opacity: isSelected ? 1.0 : 0.0,
|
||||||
|
child: const Icon(Icons.check, size: 18),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(_label(option, l10n)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.sort),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
_label(current, l10n),
|
||||||
|
style: theme.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _label(TaskSortOption option, AppLocalizations l10n) {
|
||||||
|
switch (option) {
|
||||||
|
case TaskSortOption.alphabetical:
|
||||||
|
return l10n.sortAlphabetical;
|
||||||
|
case TaskSortOption.interval:
|
||||||
|
return l10n.sortInterval;
|
||||||
|
case TaskSortOption.effort:
|
||||||
|
return l10n.sortEffort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'package:household_keeper/core/database/database.dart';
|
import 'package:household_keeper/core/database/database.dart';
|
||||||
import 'package:household_keeper/core/providers/database_provider.dart';
|
import 'package:household_keeper/core/providers/database_provider.dart';
|
||||||
|
import 'package:household_keeper/features/tasks/presentation/sort_dropdown.dart';
|
||||||
import 'package:household_keeper/features/tasks/presentation/task_providers.dart';
|
import 'package:household_keeper/features/tasks/presentation/task_providers.dart';
|
||||||
import 'package:household_keeper/features/tasks/presentation/task_row.dart';
|
import 'package:household_keeper/features/tasks/presentation/task_row.dart';
|
||||||
import 'package:household_keeper/l10n/app_localizations.dart';
|
import 'package:household_keeper/l10n/app_localizations.dart';
|
||||||
@@ -27,6 +28,7 @@ class TaskListScreen extends ConsumerWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: _RoomTitle(roomId: roomId),
|
title: _RoomTitle(roomId: roomId),
|
||||||
actions: [
|
actions: [
|
||||||
|
const SortDropdown(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () => context.go('/rooms/$roomId/edit'),
|
onPressed: () => context.go('/rooms/$roomId/edit'),
|
||||||
|
|||||||
Reference in New Issue
Block a user