Skip to content

Commit b0810de

Browse files
Planned recipes on specific dates (#686)
Co-authored-by: Tom Bursch <tombursch@gmail.com>
1 parent 6c19c35 commit b0810de

File tree

11 files changed

+123
-103
lines changed

11 files changed

+123
-103
lines changed

kitchenowl/lib/config.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:package_info_plus/package_info_plus.dart';
33

44
abstract class Config {
55
// ignore: constant_identifier_names
6-
static const int MIN_BACKEND_VERSION = 98;
6+
static const int MIN_BACKEND_VERSION = 109;
77
static const String defaultServer = "https://app.kitchenowl.org";
88
static Future<PackageInfo?>? _packageInfo; // Gets loaded by SettingsCubit
99
static PackageInfo? _packageInfoSync;

kitchenowl/lib/cubits/planner_cubit.dart

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@ class PlannerCubit extends Cubit<PlannerCubitState> {
1717
refresh();
1818
}
1919

20-
Future<void> remove(Recipe recipe, [int? day]) async {
20+
Future<void> remove(Recipe recipe, [DateTime? cookingDate]) async {
2121
await TransactionHandler.getInstance()
2222
.runTransaction(TransactionPlannerRemoveRecipe(
2323
household: household,
2424
recipe: recipe,
25-
day: day,
25+
cookingDate: cookingDate,
2626
));
2727
await refresh();
2828
}
2929

30-
Future<void> add(Recipe recipe, [int? day]) async {
30+
Future<void> add(Recipe recipe, [DateTime? cookingDate]) async {
3131
await TransactionHandler.getInstance()
3232
.runTransaction(TransactionPlannerAddRecipe(
3333
household: household,
34-
recipePlan: RecipePlan(recipe: recipe, day: day),
34+
recipePlan: RecipePlan(recipe: recipe, cookingDate: cookingDate),
3535
));
3636
await refresh();
3737
}
@@ -124,11 +124,28 @@ class LoadedPlannerCubitState extends PlannerCubitState {
124124

125125
List<RecipePlan> getPlannedWithoutDay() {
126126
return recipePlans
127-
.where((element) => element.day == null || element.day! < 0)
127+
.where((element) =>
128+
element.cookingDate == null ||
129+
(element.cookingDate != null &&
130+
element.cookingDate!.millisecondsSinceEpoch < 0))
128131
.toList();
129132
}
130133

131-
List<RecipePlan> getPlannedOfDay(int day) {
132-
return recipePlans.where((element) => element.day == day).toList();
134+
List<RecipePlan> getPlannedOfDate(DateTime cookingDate) {
135+
return recipePlans
136+
.where((element) => element.cookingDate?.day == cookingDate.day)
137+
.toList();
138+
}
139+
140+
List<DateTime> getUniqueCookingDays() {
141+
Set<DateTime> uniqueDays = {};
142+
143+
for (var recipe in recipePlans) {
144+
if (recipe.cookingDate != null &&
145+
recipe.cookingDate!.millisecondsSinceEpoch > 0) {
146+
uniqueDays.add(recipe.cookingDate!);
147+
}
148+
}
149+
return uniqueDays.toList()..sort();
133150
}
134151
}

kitchenowl/lib/cubits/recipe_cubit.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,14 @@ class RecipeCubit extends Cubit<RecipeState> {
9292
}
9393
}
9494

95-
Future<void> addRecipeToPlanner({int? day, bool updateOnAdd = false}) async {
95+
Future<void> addRecipeToPlanner(
96+
{DateTime? cookingDate, bool updateOnAdd = false}) async {
9697
if (state.household != null) {
9798
await _transactionHandler.runTransaction(TransactionPlannerAddRecipe(
9899
household: state.household!,
99100
recipePlan: RecipePlan(
100101
recipe: state.recipe,
101-
day: day,
102+
cookingDate: cookingDate,
102103
yields: state.selectedYields != null &&
103104
state.recipe.yields != state.selectedYields &&
104105
state.selectedYields! > 0

kitchenowl/lib/l10n/app_de.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@
744744
"wrongUsernameOrPassword": "Benutzername oder Passwort ist falsch",
745745
"yearly": "Jährlich",
746746
"yes": "Ja",
747+
"yesterday": "Gestern",
747748
"yields": "Portionen",
748749
"you": "du"
749750
}

kitchenowl/lib/l10n/app_en.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@
748748
"wrongUsernameOrPassword": "Wrong username or password",
749749
"yearly": "Yearly",
750750
"yes": "Yes",
751+
"yesterday": "Yesterday",
751752
"yields": "Yields",
752753
"you": "you"
753754
}

kitchenowl/lib/models/planner.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,37 @@ import 'package:kitchenowl/models/recipe.dart';
33

44
class RecipePlan extends Model {
55
final Recipe recipe;
6-
final int? day;
6+
final DateTime? cookingDate;
77
final int? yields;
88

9-
const RecipePlan({
9+
RecipePlan({
1010
required this.recipe,
11-
this.day,
11+
this.cookingDate,
1212
this.yields,
1313
});
1414

1515
factory RecipePlan.fromJson(Map<String, dynamic> map) {
1616
return RecipePlan(
1717
recipe: Recipe.fromJson(map['recipe']),
18-
day: map['day'],
18+
cookingDate:
19+
DateTime.fromMillisecondsSinceEpoch(map["cooking_date"], isUtc: true),
1920
yields: map['yields'],
2021
);
2122
}
2223

2324
Recipe get recipeWithYields {
2425
if (yields == null || yields! <= 0) return recipe;
25-
2626
return recipe.withYields(yields!);
2727
}
2828

2929
@override
30-
List<Object?> get props => [recipe, day, yields];
30+
List<Object?> get props => [recipe, cookingDate, yields];
3131

3232
@override
3333
Map<String, dynamic> toJson() => {
3434
"recipe_id": recipe.id,
35-
if (day != null) "day": day,
35+
if (cookingDate != null)
36+
"cooking_date": cookingDate?.millisecondsSinceEpoch,
3637
if (yields != null) "yields": yields,
3738
};
3839

kitchenowl/lib/models/recipe.dart

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Recipe extends Model {
1010
final String name;
1111
final String description;
1212
final bool isPlanned;
13-
final Set<int> plannedDays;
13+
final Set<DateTime> plannedCookingDates;
1414
final int time;
1515
final int cookTime;
1616
final int prepTime;
@@ -40,7 +40,7 @@ class Recipe extends Model {
4040
this.imageHash,
4141
this.items = const [],
4242
this.tags = const {},
43-
this.plannedDays = const {},
43+
this.plannedCookingDates = const {},
4444
this.public = false,
4545
this.householdId,
4646
this.household,
@@ -55,9 +55,20 @@ class Recipe extends Model {
5555
if (map.containsKey('tags')) {
5656
tags = Set.from(map['tags'].map((e) => Tag.fromJson(e)));
5757
}
58-
Set<int> plannedDays = const {};
59-
if (map.containsKey('planned_days')) {
60-
plannedDays = Set.from(map['planned_days']);
58+
59+
Set<DateTime> plannedCookingDates = {};
60+
61+
if (map.containsKey('planned_cooking_dates') &&
62+
map['planned_cooking_dates'] is List) {
63+
for (var timestamp in map['planned_cooking_dates']) {
64+
// Check if the timestamp is not null
65+
if (timestamp != null) {
66+
// Convert milliseconds to DateTime and add to the Set
67+
DateTime dateTime =
68+
DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true);
69+
plannedCookingDates.add(dateTime);
70+
}
71+
}
6172
}
6273

6374
return Recipe(
@@ -76,7 +87,7 @@ class Recipe extends Model {
7687
householdId: map['household_id'],
7788
items: items,
7889
tags: tags,
79-
plannedDays: plannedDays,
90+
plannedCookingDates: plannedCookingDates,
8091
household: map.containsKey("household")
8192
? Household.fromJson(map['household'])
8293
: null,
@@ -96,7 +107,7 @@ class Recipe extends Model {
96107
bool? public,
97108
List<RecipeItem>? items,
98109
Set<Tag>? tags,
99-
Set<int>? plannedDays,
110+
Set<DateTime>? plannedCookingDates,
100111
int? householdId,
101112
}) =>
102113
Recipe(
@@ -113,7 +124,7 @@ class Recipe extends Model {
113124
imageHash: imageHash,
114125
image: image ?? this.image,
115126
tags: tags ?? this.tags,
116-
plannedDays: plannedDays ?? this.plannedDays,
127+
plannedCookingDates: plannedCookingDates ?? this.plannedCookingDates,
117128
public: public ?? this.public,
118129
householdId: householdId ?? this.householdId,
119130
household: this.household,
@@ -144,7 +155,7 @@ class Recipe extends Model {
144155
imageHash,
145156
tags,
146157
items,
147-
plannedDays,
158+
plannedCookingDates,
148159
public,
149160
householdId,
150161
household,
@@ -173,7 +184,7 @@ class Recipe extends Model {
173184
"items": items.map((e) => e.toJsonWithId()).toList(),
174185
"tags": tags.map((e) => e.toJsonWithId()).toList(),
175186
if (imageHash != null) "photo_hash": imageHash,
176-
"planned_days": plannedDays.toList(),
187+
"planned_cooking_dates": plannedCookingDates.toList(),
177188
"household_id": householdId,
178189
});
179190

kitchenowl/lib/pages/household_page/planner.dart

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ import 'package:kitchenowl/widgets/recipe_card.dart';
1717
import 'package:responsive_builder/responsive_builder.dart';
1818
import 'package:tuple/tuple.dart';
1919

20-
int db_weekday(int shift) {
21-
// subtract 1 because DateTime.weekday goes from 1 to 7. Kitchenowl-db from 0 to 6
22-
return DateTime.now().add(Duration(days: shift)).weekday - 1;
20+
DateTime toEndOfDay(DateTime dt) {
21+
return DateTime(dt.year, dt.month, dt.day, 23, 59, 59);
2322
}
2423

25-
String formatDateAsWeekday(DateTime date, BuildContext context,
26-
{String default_format = 'EEEE'}) {
24+
String formatDateAsWeekday(
25+
BuildContext context,
26+
DateTime date, {
27+
String default_format = 'EEEE',
28+
}) {
2729
DateTime today = DateTime.now();
2830
DateTime tomorrow = today.add(Duration(days: 1));
31+
DateTime yesterday = today.subtract(Duration(days: 1));
2932

3033
// Check if the date is today or tomorrow
3134
if (date.year == today.year &&
@@ -36,12 +39,32 @@ String formatDateAsWeekday(DateTime date, BuildContext context,
3639
date.month == tomorrow.month &&
3740
date.day == tomorrow.day) {
3841
return AppLocalizations.of(context)!.tomorrow;
42+
} else if (date.year == yesterday.year &&
43+
date.month == yesterday.month &&
44+
date.day == yesterday.day) {
45+
return AppLocalizations.of(context)!.yesterday;
3946
} else {
4047
// Return the weekday name
4148
return DateFormat(default_format).format(date);
4249
}
4350
}
4451

52+
String _formatDate(BuildContext context, int daysToAdd) {
53+
DateTime date = DateTime.now().add(Duration(days: daysToAdd));
54+
if (daysToAdd >= -1 && daysToAdd < 7) {
55+
return formatDateAsWeekday(context, date, default_format: 'E');
56+
} else {
57+
return DateFormat.yMMMd().format(date);
58+
}
59+
}
60+
61+
int daysBetween(DateTime from, DateTime to) {
62+
// otherwise it's rounded
63+
from = DateTime(from.year, from.month, from.day);
64+
to = DateTime(to.year, to.month, to.day);
65+
return (to.difference(from).inHours / 24).round();
66+
}
67+
4568
class PlannerPage extends StatefulWidget {
4669
const PlannerPage({super.key});
4770

@@ -188,9 +211,10 @@ class _PlannerPageState extends State<PlannerPage> {
188211
),
189212
),
190213
),
191-
for (int i = 0; i < 7; i++)
214+
for (final cookingDate
215+
in state.getUniqueCookingDays())
192216
for (final plan
193-
in state.getPlannedOfDay(db_weekday(i)))
217+
in state.getPlannedOfDate(cookingDate))
194218
KitchenOwlFractionallySizedBox(
195219
widthFactor: (1 /
196220
DynamicStyling.itemCrossAxisCount(
@@ -206,13 +230,13 @@ class _PlannerPageState extends State<PlannerPage> {
206230
CrossAxisAlignment.stretch,
207231
children: [
208232
if (plan ==
209-
state.getPlannedOfDay(
210-
db_weekday(i))[0])
233+
state
234+
.getPlannedOfDate(cookingDate)[0])
211235
Padding(
212236
padding:
213237
const EdgeInsets.only(top: 5),
214238
child: Text(
215-
'${formatDateAsWeekday(DateTime.now().add(Duration(days: i)), context, default_format: 'E')}',
239+
'${_formatDate(context, daysBetween(DateTime.now(), cookingDate))}',
216240
style: Theme.of(context)
217241
.textTheme
218242
.bodyLarge,
@@ -230,7 +254,7 @@ class _PlannerPageState extends State<PlannerPage> {
230254
onPressed: () {
231255
cubit.remove(
232256
plan.recipe,
233-
db_weekday(i),
257+
plan.cookingDate,
234258
);
235259
},
236260
onLongPressed: () => _openRecipePage(
@@ -421,23 +445,16 @@ class _PlannerPageState extends State<PlannerPage> {
421445
PlannerCubit cubit,
422446
Recipe recipe,
423447
) async {
424-
int? day = await showDialog<int>(
448+
final DateTime? cookingDate = await showDatePicker(
425449
context: context,
426-
builder: (context) => SelectDialog(
427-
title: AppLocalizations.of(context)!.addRecipeToPlannerShort,
428-
cancelText: AppLocalizations.of(context)!.cancel,
429-
options: List.generate(7, (index) {
430-
return SelectDialogOption(
431-
db_weekday(index),
432-
formatDateAsWeekday(
433-
DateTime.now().add(Duration(days: index)), context));
434-
}),
435-
),
450+
initialDate: DateTime.now(),
451+
firstDate: DateTime(2000),
452+
lastDate: DateTime.now().add(const Duration(days: 400)),
436453
);
437-
if (day != null) {
454+
if (cookingDate != null) {
438455
await cubit.add(
439456
recipe,
440-
day >= 0 ? day : null,
457+
cookingDate.millisecondsSinceEpoch > 0 ? toEndOfDay(cookingDate) : null,
441458
);
442459
}
443460
}

0 commit comments

Comments
 (0)