aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/main.dart5
-rw-r--r--lib/models/data/recipe_data_class.dart15
-rw-r--r--lib/models/data/settings_data_class.dart2
-rw-r--r--lib/util/file_handler.dart15
-rw-r--r--lib/util/notifications.dart4
-rw-r--r--lib/views/info_view.dart36
-rw-r--r--lib/views/main_view.dart2
-rw-r--r--lib/views/recipe_view.dart72
-rw-r--r--lib/views/settings_view.dart4
-rw-r--r--lib/widgets/recipe_card_widget.dart5
-rw-r--r--lib/widgets/utility_icon_row_widget.dart29
11 files changed, 97 insertions, 92 deletions
diff --git a/lib/main.dart b/lib/main.dart
index d22d644..9bbfe49 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kulinar_app/constants.dart';
-import 'package:kulinar_app/models/data/shoplist_data_class.dart';
import 'package:kulinar_app/views/main_view.dart';
import 'package:kulinar_app/util/notifications.dart';
import 'package:kulinar_app/models/data/recipe_data_class.dart';
+import 'package:kulinar_app/models/data/shoplist_data_class.dart';
import 'package:kulinar_app/models/data/settings_data_class.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@@ -37,11 +37,12 @@ class _AppRootState extends State<AppRoot> {
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
- statusBarColor: Colors.transparent,
+ statusBarColor: cPrimaryColor,
statusBarIconBrightness: Brightness.light,
));
return MaterialApp(
+ debugShowCheckedModeBanner: false,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
diff --git a/lib/models/data/recipe_data_class.dart b/lib/models/data/recipe_data_class.dart
index fd4ef22..3f707bc 100644
--- a/lib/models/data/recipe_data_class.dart
+++ b/lib/models/data/recipe_data_class.dart
@@ -38,14 +38,25 @@ class RecipeData {
if (_result.isEmpty) return;
+ recipesIdentical(Recipe a, Recipe b) {
+ if (a.title != b.title) return false;
+ if (a.description != b.description) return false;
+
+ return true;
+ }
+
_result.forEach((item) {
- recipeList.add(Recipe(
+ Recipe recipe = Recipe(
title: item["title"],
description: item["description"],
favorite: item["favorite"],
rating: item["rating"],
image: item["image"],
- ));
+ );
+
+ if (RecipeData.recipeList.where((element) => recipesIdentical(element, recipe)).isEmpty) recipeList.add(recipe);
});
+
+ save();
}
}
diff --git a/lib/models/data/settings_data_class.dart b/lib/models/data/settings_data_class.dart
index e8cd604..5d3de60 100644
--- a/lib/models/data/settings_data_class.dart
+++ b/lib/models/data/settings_data_class.dart
@@ -29,5 +29,7 @@ class SettingsData {
_result.forEach((key, value) {
settings[key] = value;
});
+
+ save();
}
}
diff --git a/lib/util/file_handler.dart b/lib/util/file_handler.dart
index 0344c60..db1d725 100644
--- a/lib/util/file_handler.dart
+++ b/lib/util/file_handler.dart
@@ -32,25 +32,23 @@ class FileHandler {
await _file.writeAsString(jsonEncode(await encodeDataAsMap(ExportType.full, data)));
- ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportSuccess);
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportSuccess, actionLabel: "");
Notifications.notify(AppLocalizations.of(context)!.exportSuccess, AppLocalizations.of(context)!.tapHint, _file.path);
} catch (e) {
print(e);
- ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportError);
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.exportError, actionLabel: "");
}
}
}
/// Deserializes the given `file` into the returned Map. Sends a confirmation Toast to the given `context` afterwards.
- static Future<Map<String, String>> deserializeFile(BuildContext context, File file) async {
+ static Future deserializeFile(BuildContext context, File file) async {
final dynamic _content = jsonDecode(await file.readAsString());
SettingsData.decode(_content["settings"]);
RecipeData.decode(_content["recipes"]);
- ToastBar.showToastBar(context, AppLocalizations.of(context)!.importSuccess);
-
- return _content;
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.importSuccess, actionLabel: "");
}
/// Parses a given possible deserializable `file` for useful information and returns a map of it.
@@ -76,14 +74,14 @@ class FileHandler {
static Future<File?> pickDeserializableFile(BuildContext context) async {
if (await Permission.storage.request().isGranted) {
try {
- FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ["kulinar"]);
+ FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.any); //, allowedExtensions: ["kulinar"]);
if (result != null) {
return File(result.files.single.path!);
}
} catch (e) {
print(e);
- ToastBar.showToastBar(context, AppLocalizations.of(context)!.importError);
+ ToastBar.showToastBar(context, AppLocalizations.of(context)!.importError, actionLabel: "");
}
}
@@ -97,7 +95,6 @@ class FileHandler {
_map["version"] = cVersion;
_map["type"] = type.toString();
// TODO: IMPLEMENT: Base64 Images and image count on export
- // map["imageCount"] = getImageCount;
_map.addAll(data as Map<String, String>);
return _map;
diff --git a/lib/util/notifications.dart b/lib/util/notifications.dart
index 91280da..c652600 100644
--- a/lib/util/notifications.dart
+++ b/lib/util/notifications.dart
@@ -1,4 +1,4 @@
-import 'package:open_file/open_file.dart';
+// import 'package:open_file/open_file.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
/// Handles all the notification related things.
@@ -19,7 +19,7 @@ class Notifications {
/// Is called when the notification is pressed.
static Future<dynamic> selectNotification(String? payload) async {
if (payload != null) {
- OpenFile.open(payload, type: "*/*"); // TODO: FIXME: Still doesnt work (sometimes)
+ // OpenFile.open(payload, type: "*/*");
}
}
diff --git a/lib/views/info_view.dart b/lib/views/info_view.dart
index e2faeab..04c3887 100644
--- a/lib/views/info_view.dart
+++ b/lib/views/info_view.dart
@@ -14,8 +14,9 @@ class InfoView extends StatefulWidget {
}
class _InfoViewState extends State<InfoView> {
- String websiteUrl = "https://davidpkj.github.io/kulinar_app.html";
- String sourceCodeUrl = "https://github.com/davidpkj/kulinar_app";
+ String websiteURL = "https://davidpenkowoj.de";
+ String sourceCodeURL = "http://git.davidpenkowoj.de/kulinar_app.git";
+ String privacyNoticeURL = "https://davidpenkowoj.de/static/files/privacy-notice.html";
@override
Widget build(BuildContext context) {
@@ -38,26 +39,36 @@ class _InfoViewState extends State<InfoView> {
),
ListTile(
title: Text(AppLocalizations.of(context)!.info3, style: cDefaultTextStyle),
- subtitle: Text(websiteUrl, style: cDetailsTextStyle, overflow: TextOverflow.ellipsis),
+ subtitle: Text(websiteURL, style: cDetailsTextStyle, overflow: TextOverflow.ellipsis),
trailing: IconButton(
icon: Icon(Icons.north_east_rounded),
onPressed: () {
- _launchURL(Uri.parse(websiteUrl));
+ _launchURL(Uri.parse(websiteURL));
},
),
),
ListTile(
title: Text(AppLocalizations.of(context)!.info4, style: cDefaultTextStyle),
- subtitle: Text(sourceCodeUrl, style: cDetailsTextStyle, overflow: TextOverflow.ellipsis),
+ subtitle: Text(sourceCodeURL, style: cDetailsTextStyle, overflow: TextOverflow.ellipsis),
trailing: IconButton(
icon: Icon(Icons.north_east_rounded),
onPressed: () {
- _launchURL(Uri.parse(sourceCodeUrl));
+ _launchURL(Uri.parse(sourceCodeURL));
},
),
),
ListTile(
title: Text(AppLocalizations.of(context)!.info5, style: cDefaultTextStyle),
+ subtitle: Text(privacyNoticeURL, style: cDetailsTextStyle, overflow: TextOverflow.ellipsis),
+ trailing: IconButton(
+ icon: Icon(Icons.north_east_rounded),
+ onPressed: () {
+ _launchURL(Uri.parse(privacyNoticeURL));
+ },
+ ),
+ ),
+ ListTile(
+ title: Text(AppLocalizations.of(context)!.info6, style: cDefaultTextStyle),
trailing: IconButton(
icon: Icon(Icons.launch_rounded),
onPressed: () {
@@ -65,6 +76,15 @@ class _InfoViewState extends State<InfoView> {
},
),
),
+/* ListTile(
+ title: Text(AppLocalizations.of(context)!.info7, style: cDefaultTextStyle),
+ trailing: IconButton(
+ icon: Icon(Icons.launch_rounded),
+ onPressed: () {
+ Navigator.push(context, SlideFromRightRoute(child: ChangelogView()));
+ },
+ ),
+ ), */
],
),
);
@@ -81,9 +101,9 @@ class _InfoViewState extends State<InfoView> {
_launchURL(Uri url) async {
if (await canLaunchUrl(url)) {
- await launchUrl(url);
+ await launchUrl(url, mode: LaunchMode.externalApplication);
} else {
- throw 'Could not launch $url';
+ print('Could not launch $url');
}
}
}
diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart
index 678db4a..97ad04e 100644
--- a/lib/views/main_view.dart
+++ b/lib/views/main_view.dart
@@ -90,7 +90,7 @@ class _MainViewState extends State<MainView> with SingleTickerProviderStateMixin
onPressed: () async {
await Navigator.push(
context,
- SlideFromBottomRoute(child: RecipeView(readonly: false)),
+ SlideFromBottomRoute(child: RecipeView()),
);
setState(() {});
diff --git a/lib/views/recipe_view.dart b/lib/views/recipe_view.dart
index a0304c3..92a50b7 100644
--- a/lib/views/recipe_view.dart
+++ b/lib/views/recipe_view.dart
@@ -3,13 +3,13 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:kulinar_app/constants.dart';
-import 'package:kulinar_app/models/data/shoplist_data_class.dart';
import 'package:kulinar_app/views/image_view.dart';
import 'package:kulinar_app/models/recipe_class.dart';
import 'package:kulinar_app/widgets/toastbar_widget.dart';
import 'package:kulinar_app/models/data/recipe_data_class.dart';
import 'package:kulinar_app/widgets/page_route_transitions.dart';
import 'package:kulinar_app/widgets/utility_icon_row_widget.dart';
+import 'package:kulinar_app/models/data/shoplist_data_class.dart';
import 'package:kulinar_app/models/data/settings_data_class.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
@@ -21,10 +21,9 @@ import 'package:http/http.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class RecipeView extends StatefulWidget {
- const RecipeView({Key? key, this.remote = false, required this.readonly, this.recipe, this.redrawCallback, this.showToastCallback}) : super(key: key);
+ const RecipeView({Key? key, this.remote = false, this.recipe, this.redrawCallback, this.showToastCallback}) : super(key: key);
final bool remote;
- final bool readonly;
final Recipe? recipe;
final Function? redrawCallback;
final Function? showToastCallback;
@@ -41,7 +40,7 @@ class _RecipeViewState extends State<RecipeView> {
final FocusNode _detailsTitleFocus = FocusNode();
final FocusNode _detailsTextFocus = FocusNode();
Recipe _unsavedRecipe = Recipe();
- bool _readonlyOverride = false;
+ bool _isEditModeEnabled = false;
Recipe? _removedRecipe;
@override
@@ -52,6 +51,7 @@ class _RecipeViewState extends State<RecipeView> {
if (_unsavedRecipe.isDefault()) {
_controller1.clear();
_controller2.clear();
+ _isEditModeEnabled = true;
} else {
_controller1.text = _unsavedRecipe.title ?? "";
_controller2.text = _unsavedRecipe.description ?? "";
@@ -97,16 +97,16 @@ class _RecipeViewState extends State<RecipeView> {
_unsavedRecipe.title = _controller1.text;
_unsavedRecipe.description = _controller2.text;
- if (!widget.readonly) {
- RecipeData.recipeList.add(_unsavedRecipe);
+ if (_isEditModeEnabled) {
+ if (!RecipeData.recipeList.contains(_unsavedRecipe)) RecipeData.recipeList.add(_unsavedRecipe);
RecipeData.save();
}
+ _isEditModeEnabled = false;
+
if (widget.recipe == null) {
Navigator.pop(context);
} else {
- _readonlyOverride = false;
-
setState(() {});
}
}
@@ -131,13 +131,6 @@ class _RecipeViewState extends State<RecipeView> {
setState(() {});
}
- bool _isEditingAllowed() {
- if (_readonlyOverride) return true;
- if (!widget.readonly) return true;
-
- return false;
- }
-
// TODO: FIXME: This might introduce bugs later (sanetize); maybe use FileHandler?
void downloadRecipe(BuildContext context, Recipe recipe) async {
RecipeData.recipeList.add(recipe);
@@ -186,13 +179,12 @@ class _RecipeViewState extends State<RecipeView> {
// _buildAppBarPhotoActions(recipe),
return UtilityIconRow(
remote: widget.remote,
- readonly: !_isEditingAllowed(),
+ isEditModeEnabled: _isEditModeEnabled,
unsavedRecipe: _unsavedRecipe,
pickImageCallback: _pickImage,
removeImageCallback: _removeImage,
removeRecipeCallback: _removeRecipe,
downloadRecipeCallback: downloadRecipe,
- isEditingAllowedCallback: _isEditingAllowed,
cacheUnsavedRecipeCallback: _cacheUnsavedRecipe,
);
}
@@ -205,7 +197,7 @@ class _RecipeViewState extends State<RecipeView> {
controller: _controller1,
style: cRecipeTitleStyle,
cursorColor: cPrimaryColor,
- enabled: _isEditingAllowed(),
+ enabled: _isEditModeEnabled,
focusNode: _detailsTitleFocus,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
@@ -257,7 +249,7 @@ class _RecipeViewState extends State<RecipeView> {
Widget _buildDescriptionInput() {
return Padding(
padding: const EdgeInsets.all(18.0),
- child: _isEditingAllowed() ? _buildInputDescriptuion() : _buildMarkdownDescription(),
+ child: _isEditModeEnabled ? _buildInputDescriptuion() : _buildMarkdownDescription(),
);
}
@@ -287,30 +279,7 @@ class _RecipeViewState extends State<RecipeView> {
selectable: true,
onTapLink: (text, href, title) => null,
imageBuilder: (uri, title, alt) => Container(),
- styleSheet: MarkdownStyleSheet(
- tableBody: cRecipeDescriptionStyle,
- a: cRecipeDescriptionStyle,
- p: cRecipeDescriptionStyle,
- h1: cRecipeSubtitleStyle,
- h2: cRecipeSubtitleStyle,
- h3: cRecipeSubtitleStyle,
- h4: cRecipeSubtitleStyle,
- h5: cRecipeSubtitleStyle,
- h6: cRecipeSubtitleStyle,
-/* TODO: remove code and quote block
- blockquoteDecoration: BoxDecoration(color: Color.fromARGB(255, 255, 13, 13)),
- code: cDetailsTextStyle,
-*/
- listIndent: 15.0,
- listBullet: cRecipeDescriptionStyle,
- listBulletPadding: EdgeInsets.only(right: 10.0),
- horizontalRuleDecoration: BoxDecoration(
- border: Border.all(
- color: Colors.grey,
- width: 0.5,
- ),
- ),
- ),
+ // styleSheet: CustomMarkdownStyle(),
);
}
@@ -321,6 +290,12 @@ class _RecipeViewState extends State<RecipeView> {
void _addToShoppingList(BuildContext _) {
final _ingredients = _unsavedRecipe.description!.split("\n")..retainWhere((element) => element.startsWith("- "));
+ if (_ingredients.isEmpty) {
+ // TODO: translation
+ widget.showToastCallback!("No ingredients found", "");
+ return;
+ }
+
for (String _ingredient in _ingredients) {
String _item = _ingredient.substring(2).trim();
@@ -333,10 +308,7 @@ class _RecipeViewState extends State<RecipeView> {
if (widget.showToastCallback != null) {
// TODO: translation
- widget.showToastCallback!("Added to Shoplist", "Ok", () {
- _addRecipe(passedRecipe: _removedRecipe);
- widget.redrawCallback!();
- });
+ widget.showToastCallback!("Added to Shoplist", "");
}
}
@@ -403,7 +375,7 @@ class _RecipeViewState extends State<RecipeView> {
PreferredSizeWidget _buildAppBar(BuildContext context, Recipe recipe) {
String _title = AppLocalizations.of(context)!.mode1;
- if (_isEditingAllowed()) _title = AppLocalizations.of(context)!.mode2;
+ if (_isEditModeEnabled) _title = AppLocalizations.of(context)!.mode2;
return AppBar(
title: Text(_title),
@@ -418,7 +390,7 @@ class _RecipeViewState extends State<RecipeView> {
}
Widget _buildAppBarCheckActions() {
- if (_isEditingAllowed()) {
+ if (_isEditModeEnabled) {
return IconButton(
icon: Icon(Icons.check),
onPressed: _addRecipe,
@@ -427,7 +399,7 @@ class _RecipeViewState extends State<RecipeView> {
return IconButton(
icon: Icon(Icons.edit),
onPressed: () {
- _readonlyOverride = true;
+ _isEditModeEnabled = true;
setState(() {});
},
diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart
index d217bb7..9bbde49 100644
--- a/lib/views/settings_view.dart
+++ b/lib/views/settings_view.dart
@@ -80,7 +80,9 @@ class _SettingsViewState extends State<SettingsView> {
onPressed: () async {
final _file = await FileHandler.pickDeserializableFile(context);
- await FileHandler.deserializeFile(context, _file!);
+ if (_file == null) return;
+
+ await FileHandler.deserializeFile(context, _file);
setState(() {});
},
diff --git a/lib/widgets/recipe_card_widget.dart b/lib/widgets/recipe_card_widget.dart
index 22e50ef..4828b9a 100644
--- a/lib/widgets/recipe_card_widget.dart
+++ b/lib/widgets/recipe_card_widget.dart
@@ -56,7 +56,6 @@ class _RecipeCardState extends State<RecipeCard> {
SlideFromBottomRoute(
child: RecipeView(
remote: widget.remote,
- readonly: true,
recipe: widget.recipe,
redrawCallback: widget.redrawCallback,
showToastCallback: widget.showToastCallback,
@@ -103,10 +102,10 @@ class _RecipeInfo extends StatefulWidget {
final Recipe recipe;
@override
- __RecipeInfoState createState() => __RecipeInfoState();
+ _RecipeInfoState createState() => _RecipeInfoState();
}
-class __RecipeInfoState extends State<_RecipeInfo> {
+class _RecipeInfoState extends State<_RecipeInfo> {
@override
Widget build(BuildContext context) {
return Row(
diff --git a/lib/widgets/utility_icon_row_widget.dart b/lib/widgets/utility_icon_row_widget.dart
index 3d45d15..c192354 100644
--- a/lib/widgets/utility_icon_row_widget.dart
+++ b/lib/widgets/utility_icon_row_widget.dart
@@ -9,24 +9,22 @@ class UtilityIconRow extends StatefulWidget {
const UtilityIconRow({
Key? key,
this.remote = false,
- required this.readonly,
required this.unsavedRecipe,
+ required this.isEditModeEnabled,
required this.pickImageCallback,
required this.removeImageCallback,
required this.removeRecipeCallback,
required this.downloadRecipeCallback,
- required this.isEditingAllowedCallback,
required this.cacheUnsavedRecipeCallback,
}) : super(key: key);
final bool remote;
- final bool readonly;
+ final bool isEditModeEnabled;
final Recipe unsavedRecipe;
final Function pickImageCallback;
final Function removeImageCallback;
final Function removeRecipeCallback;
final Function downloadRecipeCallback;
- final Function isEditingAllowedCallback;
final Function cacheUnsavedRecipeCallback;
@override
@@ -44,18 +42,21 @@ class _UtilityIconRowState extends State<UtilityIconRow> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
- padding: const EdgeInsets.all(0), // EdgeInsets.only(left: 4.0),
+ padding: const EdgeInsets.only(left: 6.0),
child: _buildButtonAddImage(),
),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- _buildIconRating(context),
- _buildIconFavorite(),
- // _buildIconCalculate(),
- // _buildIconShare(),
- // _buildIconImportExport(context),
- ],
+ Padding(
+ padding: const EdgeInsets.only(right: 6.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ _buildIconRating(context),
+ _buildIconFavorite(),
+ // _buildIconCalculate(),
+ // _buildIconShare(),
+ // _buildIconImportExport(context),
+ ],
+ ),
),
],
),