Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a84f395
init when-field in planner
crosspolar Feb 15, 2025
2551bcf
remove comment
crosspolar Feb 15, 2025
c6fc8b5
remove "day" again. Tests still failing
crosspolar Feb 16, 2025
bfb947d
backend: select bs whole day
crosspolar Feb 20, 2025
10d3fc4
tests
crosspolar Feb 20, 2025
df8a1ff
set to end of day
crosspolar Feb 20, 2025
3ab5d8e
return days for backward compatibility
crosspolar Feb 21, 2025
249f3bb
remove second test
crosspolar Feb 23, 2025
d6a4bf7
use pytest.configure
crosspolar Feb 23, 2025
e5c9543
"fix" tests
crosspolar Feb 23, 2025
b8476c5
test for second recipe with day
crosspolar Feb 23, 2025
8413652
when is reserved keyword, rename to cooking_date
crosspolar Feb 23, 2025
b65039a
add primary key
crosspolar Feb 23, 2025
bcbac78
fix tests
crosspolar Feb 23, 2025
6b98bfd
data migration
crosspolar Feb 23, 2025
b01627f
planned_cooking_dates
crosspolar Mar 9, 2025
9304d13
remove data migration for now
crosspolar Mar 9, 2025
129a248
raise backend-version
crosspolar Mar 21, 2025
1447a1c
serialize datetime for planned recipe transaction
crosspolar Mar 21, 2025
b94d8ed
fix tests
crosspolar Mar 21, 2025
cfb04eb
frontend start
crosspolar Mar 7, 2025
d14f9ad
fix: day-parameter walways exists but can be null
crosspolar Mar 9, 2025
b07183c
if day is minus 1 transform into date_min
crosspolar Mar 9, 2025
a588d21
if (cooking_date != null) "cooking_date": cooking_date?.toIs…
crosspolar Mar 9, 2025
836cc39
remove datetime_min since it's now compared to epoch
crosspolar Mar 14, 2025
08b393f
remove print
crosspolar Mar 14, 2025
997967e
replace selectdialog with datepicker
crosspolar Mar 14, 2025
2797bde
use datepicker correctly
crosspolar Mar 14, 2025
47470a6
only get recipe daates which are not null
crosspolar Mar 17, 2025
1dc575c
remove backward compatibility and raise MIN_BACKEND_VERSION
crosspolar Mar 21, 2025
6e78e92
serialize datetime for planned recipe transaction
crosspolar Mar 21, 2025
70ad2ff
remove comment
crosspolar Mar 21, 2025
10d05fc
add datepicker to recipe_page
crosspolar Mar 21, 2025
b99e74a
format yesterday
crosspolar Mar 21, 2025
491e4a3
use whole date to find unique days
crosspolar Mar 21, 2025
8a43939
use utc in fromJson
crosspolar Mar 21, 2025
4bcbdce
remove unused import
crosspolar Mar 21, 2025
0c2da74
Merge branch 'main' into datetime_planner_app
crosspolar Apr 19, 2025
e2b05b6
Fix after merge
TomBursch Apr 19, 2025
c05efc3
Minor
TomBursch Apr 19, 2025
72a0731
Cleanup
TomBursch Apr 19, 2025
cfdaf16
Cleanup
TomBursch Apr 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kitchenowl/lib/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:package_info_plus/package_info_plus.dart';

abstract class Config {
// ignore: constant_identifier_names
static const int MIN_BACKEND_VERSION = 98;
static const int MIN_BACKEND_VERSION = 109;
static const String defaultServer = "https://app.kitchenowl.org";
static Future<PackageInfo?>? _packageInfo; // Gets loaded by SettingsCubit
static PackageInfo? _packageInfoSync;
Expand Down
31 changes: 24 additions & 7 deletions kitchenowl/lib/cubits/planner_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ class PlannerCubit extends Cubit<PlannerCubitState> {
refresh();
}

Future<void> remove(Recipe recipe, [int? day]) async {
Future<void> remove(Recipe recipe, [DateTime? cookingDate]) async {
await TransactionHandler.getInstance()
.runTransaction(TransactionPlannerRemoveRecipe(
household: household,
recipe: recipe,
day: day,
cookingDate: cookingDate,
));
await refresh();
}

Future<void> add(Recipe recipe, [int? day]) async {
Future<void> add(Recipe recipe, [DateTime? cookingDate]) async {
await TransactionHandler.getInstance()
.runTransaction(TransactionPlannerAddRecipe(
household: household,
recipePlan: RecipePlan(recipe: recipe, day: day),
recipePlan: RecipePlan(recipe: recipe, cookingDate: cookingDate),
));
await refresh();
}
Expand Down Expand Up @@ -124,11 +124,28 @@ class LoadedPlannerCubitState extends PlannerCubitState {

List<RecipePlan> getPlannedWithoutDay() {
return recipePlans
.where((element) => element.day == null || element.day! < 0)
.where((element) =>
element.cookingDate == null ||
(element.cookingDate != null &&
element.cookingDate!.millisecondsSinceEpoch < 0))
.toList();
}

List<RecipePlan> getPlannedOfDay(int day) {
return recipePlans.where((element) => element.day == day).toList();
List<RecipePlan> getPlannedOfDate(DateTime cookingDate) {
return recipePlans
.where((element) => element.cookingDate?.day == cookingDate.day)
.toList();
}

List<DateTime> getUniqueCookingDays() {
Set<DateTime> uniqueDays = {};

for (var recipe in recipePlans) {
if (recipe.cookingDate != null &&
recipe.cookingDate!.millisecondsSinceEpoch > 0) {
uniqueDays.add(recipe.cookingDate!);
}
}
return uniqueDays.toList()..sort();
}
}
5 changes: 3 additions & 2 deletions kitchenowl/lib/cubits/recipe_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,14 @@ class RecipeCubit extends Cubit<RecipeState> {
}
}

Future<void> addRecipeToPlanner({int? day, bool updateOnAdd = false}) async {
Future<void> addRecipeToPlanner(
{DateTime? cookingDate, bool updateOnAdd = false}) async {
if (state.household != null) {
await _transactionHandler.runTransaction(TransactionPlannerAddRecipe(
household: state.household!,
recipePlan: RecipePlan(
recipe: state.recipe,
day: day,
cookingDate: cookingDate,
yields: state.selectedYields != null &&
state.recipe.yields != state.selectedYields &&
state.selectedYields! > 0
Expand Down
1 change: 1 addition & 0 deletions kitchenowl/lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@
"wrongUsernameOrPassword": "Benutzername oder Passwort ist falsch",
"yearly": "Jährlich",
"yes": "Ja",
"yesterday": "Gestern",
"yields": "Portionen",
"you": "du"
}
1 change: 1 addition & 0 deletions kitchenowl/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,7 @@
"wrongUsernameOrPassword": "Wrong username or password",
"yearly": "Yearly",
"yes": "Yes",
"yesterday": "Yesterday",
"yields": "Yields",
"you": "you"
}
15 changes: 8 additions & 7 deletions kitchenowl/lib/models/planner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,37 @@ import 'package:kitchenowl/models/recipe.dart';

class RecipePlan extends Model {
final Recipe recipe;
final int? day;
final DateTime? cookingDate;
final int? yields;

const RecipePlan({
RecipePlan({
required this.recipe,
this.day,
this.cookingDate,
this.yields,
});

factory RecipePlan.fromJson(Map<String, dynamic> map) {
return RecipePlan(
recipe: Recipe.fromJson(map['recipe']),
day: map['day'],
cookingDate:
DateTime.fromMillisecondsSinceEpoch(map["cooking_date"], isUtc: true),
yields: map['yields'],
);
}

Recipe get recipeWithYields {
if (yields == null || yields! <= 0) return recipe;

return recipe.withYields(yields!);
}

@override
List<Object?> get props => [recipe, day, yields];
List<Object?> get props => [recipe, cookingDate, yields];

@override
Map<String, dynamic> toJson() => {
"recipe_id": recipe.id,
if (day != null) "day": day,
if (cookingDate != null)
"cooking_date": cookingDate?.millisecondsSinceEpoch,
if (yields != null) "yields": yields,
};

Expand Down
31 changes: 21 additions & 10 deletions kitchenowl/lib/models/recipe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Recipe extends Model {
final String name;
final String description;
final bool isPlanned;
final Set<int> plannedDays;
final Set<DateTime> plannedCookingDates;
final int time;
final int cookTime;
final int prepTime;
Expand Down Expand Up @@ -40,7 +40,7 @@ class Recipe extends Model {
this.imageHash,
this.items = const [],
this.tags = const {},
this.plannedDays = const {},
this.plannedCookingDates = const {},
this.public = false,
this.householdId,
this.household,
Expand All @@ -55,9 +55,20 @@ class Recipe extends Model {
if (map.containsKey('tags')) {
tags = Set.from(map['tags'].map((e) => Tag.fromJson(e)));
}
Set<int> plannedDays = const {};
if (map.containsKey('planned_days')) {
plannedDays = Set.from(map['planned_days']);

Set<DateTime> plannedCookingDates = {};

if (map.containsKey('planned_cooking_dates') &&
map['planned_cooking_dates'] is List) {
for (var timestamp in map['planned_cooking_dates']) {
// Check if the timestamp is not null
if (timestamp != null) {
// Convert milliseconds to DateTime and add to the Set
DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true);
plannedCookingDates.add(dateTime);
}
}
}

return Recipe(
Expand All @@ -76,7 +87,7 @@ class Recipe extends Model {
householdId: map['household_id'],
items: items,
tags: tags,
plannedDays: plannedDays,
plannedCookingDates: plannedCookingDates,
household: map.containsKey("household")
? Household.fromJson(map['household'])
: null,
Expand All @@ -96,7 +107,7 @@ class Recipe extends Model {
bool? public,
List<RecipeItem>? items,
Set<Tag>? tags,
Set<int>? plannedDays,
Set<DateTime>? plannedCookingDates,
int? householdId,
}) =>
Recipe(
Expand All @@ -113,7 +124,7 @@ class Recipe extends Model {
imageHash: imageHash,
image: image ?? this.image,
tags: tags ?? this.tags,
plannedDays: plannedDays ?? this.plannedDays,
plannedCookingDates: plannedCookingDates ?? this.plannedCookingDates,
public: public ?? this.public,
householdId: householdId ?? this.householdId,
household: this.household,
Expand Down Expand Up @@ -144,7 +155,7 @@ class Recipe extends Model {
imageHash,
tags,
items,
plannedDays,
plannedCookingDates,
public,
householdId,
household,
Expand Down Expand Up @@ -173,7 +184,7 @@ class Recipe extends Model {
"items": items.map((e) => e.toJsonWithId()).toList(),
"tags": tags.map((e) => e.toJsonWithId()).toList(),
if (imageHash != null) "photo_hash": imageHash,
"planned_days": plannedDays.toList(),
"planned_cooking_dates": plannedCookingDates.toList(),
"household_id": householdId,
});

Expand Down
65 changes: 41 additions & 24 deletions kitchenowl/lib/pages/household_page/planner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ import 'package:kitchenowl/widgets/recipe_card.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:tuple/tuple.dart';

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

String formatDateAsWeekday(DateTime date, BuildContext context,
{String default_format = 'EEEE'}) {
String formatDateAsWeekday(
BuildContext context,
DateTime date, {
String default_format = 'EEEE',
}) {
DateTime today = DateTime.now();
DateTime tomorrow = today.add(Duration(days: 1));
DateTime yesterday = today.subtract(Duration(days: 1));

// Check if the date is today or tomorrow
if (date.year == today.year &&
Expand All @@ -36,12 +39,32 @@ String formatDateAsWeekday(DateTime date, BuildContext context,
date.month == tomorrow.month &&
date.day == tomorrow.day) {
return AppLocalizations.of(context)!.tomorrow;
} else if (date.year == yesterday.year &&
date.month == yesterday.month &&
date.day == yesterday.day) {
return AppLocalizations.of(context)!.yesterday;
} else {
// Return the weekday name
return DateFormat(default_format).format(date);
}
}

String _formatDate(BuildContext context, int daysToAdd) {
DateTime date = DateTime.now().add(Duration(days: daysToAdd));
if (daysToAdd >= -1 && daysToAdd < 7) {
return formatDateAsWeekday(context, date, default_format: 'E');
} else {
return DateFormat.yMMMd().format(date);
}
}

int daysBetween(DateTime from, DateTime to) {
// otherwise it's rounded
from = DateTime(from.year, from.month, from.day);
to = DateTime(to.year, to.month, to.day);
return (to.difference(from).inHours / 24).round();
}

class PlannerPage extends StatefulWidget {
const PlannerPage({super.key});

Expand Down Expand Up @@ -188,9 +211,10 @@ class _PlannerPageState extends State<PlannerPage> {
),
),
),
for (int i = 0; i < 7; i++)
for (final cookingDate
in state.getUniqueCookingDays())
for (final plan
in state.getPlannedOfDay(db_weekday(i)))
in state.getPlannedOfDate(cookingDate))
KitchenOwlFractionallySizedBox(
widthFactor: (1 /
DynamicStyling.itemCrossAxisCount(
Expand All @@ -206,13 +230,13 @@ class _PlannerPageState extends State<PlannerPage> {
CrossAxisAlignment.stretch,
children: [
if (plan ==
state.getPlannedOfDay(
db_weekday(i))[0])
state
.getPlannedOfDate(cookingDate)[0])
Padding(
padding:
const EdgeInsets.only(top: 5),
child: Text(
'${formatDateAsWeekday(DateTime.now().add(Duration(days: i)), context, default_format: 'E')}',
'${_formatDate(context, daysBetween(DateTime.now(), cookingDate))}',
style: Theme.of(context)
.textTheme
.bodyLarge,
Expand All @@ -230,7 +254,7 @@ class _PlannerPageState extends State<PlannerPage> {
onPressed: () {
cubit.remove(
plan.recipe,
db_weekday(i),
plan.cookingDate,
);
},
onLongPressed: () => _openRecipePage(
Expand Down Expand Up @@ -421,23 +445,16 @@ class _PlannerPageState extends State<PlannerPage> {
PlannerCubit cubit,
Recipe recipe,
) async {
int? day = await showDialog<int>(
final DateTime? cookingDate = await showDatePicker(
context: context,
builder: (context) => SelectDialog(
title: AppLocalizations.of(context)!.addRecipeToPlannerShort,
cancelText: AppLocalizations.of(context)!.cancel,
options: List.generate(7, (index) {
return SelectDialogOption(
db_weekday(index),
formatDateAsWeekday(
DateTime.now().add(Duration(days: index)), context));
}),
),
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime.now().add(const Duration(days: 400)),
);
if (day != null) {
if (cookingDate != null) {
await cubit.add(
recipe,
day >= 0 ? day : null,
cookingDate.millisecondsSinceEpoch > 0 ? toEndOfDay(cookingDate) : null,
);
}
}
Expand Down
Loading
Loading