feat(03-01): add daily plan provider with date categorization and localization keys
- dailyPlanProvider: manual StreamProvider.autoDispose with overdue/today/tomorrow partitioning - Stable progress denominator: remaining overdue + remaining today + completedTodayCount - 10 new German localization keys for daily plan sections, progress, empty states - dart analyze clean, full test suite (66/66) passes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
56
lib/features/home/presentation/daily_plan_providers.dart
Normal file
56
lib/features/home/presentation/daily_plan_providers.dart
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:household_keeper/core/providers/database_provider.dart';
|
||||||
|
import 'package:household_keeper/features/home/domain/daily_plan_models.dart';
|
||||||
|
|
||||||
|
/// Reactive daily plan data: tasks categorized into overdue/today/tomorrow
|
||||||
|
/// with progress tracking.
|
||||||
|
///
|
||||||
|
/// Defined manually (not @riverpod) because riverpod_generator has trouble
|
||||||
|
/// with drift's generated [Task] type. Same pattern as [tasksInRoomProvider].
|
||||||
|
final dailyPlanProvider =
|
||||||
|
StreamProvider.autoDispose<DailyPlanState>((ref) {
|
||||||
|
final db = ref.watch(appDatabaseProvider);
|
||||||
|
final taskStream = db.dailyPlanDao.watchAllTasksWithRoomName();
|
||||||
|
|
||||||
|
return taskStream.asyncMap((allTasks) async {
|
||||||
|
// Get today's completion count (latest value from stream)
|
||||||
|
final completedToday =
|
||||||
|
await db.dailyPlanDao.watchCompletionsToday().first;
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
|
final dayAfterTomorrow = tomorrow.add(const Duration(days: 1));
|
||||||
|
|
||||||
|
final overdue = <TaskWithRoom>[];
|
||||||
|
final todayList = <TaskWithRoom>[];
|
||||||
|
final tomorrowList = <TaskWithRoom>[];
|
||||||
|
|
||||||
|
for (final tw in allTasks) {
|
||||||
|
final dueDate = DateTime(
|
||||||
|
tw.task.nextDueDate.year,
|
||||||
|
tw.task.nextDueDate.month,
|
||||||
|
tw.task.nextDueDate.day,
|
||||||
|
);
|
||||||
|
if (dueDate.isBefore(today)) {
|
||||||
|
overdue.add(tw);
|
||||||
|
} else if (dueDate.isBefore(tomorrow)) {
|
||||||
|
todayList.add(tw);
|
||||||
|
} else if (dueDate.isBefore(dayAfterTomorrow)) {
|
||||||
|
tomorrowList.add(tw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// totalTodayCount includes completedTodayCount so the denominator
|
||||||
|
// stays stable as tasks are completed (their nextDueDate moves to
|
||||||
|
// the future, shrinking overdue+today, but completedToday grows).
|
||||||
|
return DailyPlanState(
|
||||||
|
overdueTasks: overdue,
|
||||||
|
todayTasks: todayList,
|
||||||
|
tomorrowTasks: tomorrowList,
|
||||||
|
completedTodayCount: completedToday,
|
||||||
|
totalTodayCount: overdue.length + todayList.length + completedToday,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -68,5 +68,25 @@
|
|||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": { "type": "int" }
|
"count": { "type": "int" }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"dailyPlanProgress": "{completed} von {total} erledigt",
|
||||||
|
"@dailyPlanProgress": {
|
||||||
|
"placeholders": {
|
||||||
|
"completed": { "type": "int" },
|
||||||
|
"total": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dailyPlanSectionOverdue": "\u00dcberf\u00e4llig",
|
||||||
|
"dailyPlanSectionToday": "Heute",
|
||||||
|
"dailyPlanSectionUpcoming": "Demn\u00e4chst",
|
||||||
|
"dailyPlanUpcomingCount": "Demn\u00e4chst ({count})",
|
||||||
|
"@dailyPlanUpcomingCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": { "type": "int" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dailyPlanAllClearTitle": "Alles erledigt! \ud83c\udf1f",
|
||||||
|
"dailyPlanAllClearMessage": "Keine Aufgaben f\u00fcr heute. Genie\u00dfe den Moment!",
|
||||||
|
"dailyPlanNoOverdue": "Keine \u00fcberf\u00e4lligen Aufgaben",
|
||||||
|
"dailyPlanNoTasks": "Noch keine Aufgaben angelegt"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,6 +417,60 @@ abstract class AppLocalizations {
|
|||||||
/// In de, this message translates to:
|
/// In de, this message translates to:
|
||||||
/// **'{count} ausgewählt'**
|
/// **'{count} ausgewählt'**
|
||||||
String templatePickerSelected(int count);
|
String templatePickerSelected(int count);
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanProgress.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'{completed} von {total} erledigt'**
|
||||||
|
String dailyPlanProgress(int completed, int total);
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanSectionOverdue.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Überfällig'**
|
||||||
|
String get dailyPlanSectionOverdue;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanSectionToday.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Heute'**
|
||||||
|
String get dailyPlanSectionToday;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanSectionUpcoming.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Demnächst'**
|
||||||
|
String get dailyPlanSectionUpcoming;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanUpcomingCount.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Demnächst ({count})'**
|
||||||
|
String dailyPlanUpcomingCount(int count);
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanAllClearTitle.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Alles erledigt! 🌟'**
|
||||||
|
String get dailyPlanAllClearTitle;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanAllClearMessage.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Keine Aufgaben für heute. Genieße den Moment!'**
|
||||||
|
String get dailyPlanAllClearMessage;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanNoOverdue.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Keine überfälligen Aufgaben'**
|
||||||
|
String get dailyPlanNoOverdue;
|
||||||
|
|
||||||
|
/// No description provided for @dailyPlanNoTasks.
|
||||||
|
///
|
||||||
|
/// In de, this message translates to:
|
||||||
|
/// **'Noch keine Aufgaben angelegt'**
|
||||||
|
String get dailyPlanNoTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -178,4 +178,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String templatePickerSelected(int count) {
|
String templatePickerSelected(int count) {
|
||||||
return '$count ausgewählt';
|
return '$count ausgewählt';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String dailyPlanProgress(int completed, int total) {
|
||||||
|
return '$completed von $total erledigt';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanSectionOverdue => 'Überfällig';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanSectionToday => 'Heute';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanSectionUpcoming => 'Demnächst';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String dailyPlanUpcomingCount(int count) {
|
||||||
|
return 'Demnächst ($count)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanAllClearTitle => 'Alles erledigt! 🌟';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanAllClearMessage =>
|
||||||
|
'Keine Aufgaben für heute. Genieße den Moment!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanNoOverdue => 'Keine überfälligen Aufgaben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dailyPlanNoTasks => 'Noch keine Aufgaben angelegt';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user