aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordavidpkj <davidpenkow1@gmail.com>2022-07-30 10:10:43 +0200
committerdavidpkj <davidpenkow1@gmail.com>2022-07-30 10:10:43 +0200
commitccb8d94be2221e1cac8d0e36a4464418ce03aec5 (patch)
treebb042bbe1c8fdfe0c0630fadd7bd3582d3436ee8
parentd282f4bb380ce9c445d6bd3a4c9f001bb6b5f501 (diff)
updated flutter, new recipe layout
-rw-r--r--README.md32
-rw-r--r--analysis_options.yaml2
-rw-r--r--lib/constants.dart20
-rw-r--r--lib/l10n/app_de.arb4
-rw-r--r--lib/l10n/app_en.arb16
-rw-r--r--lib/l10n/app_ru.arb4
-rw-r--r--lib/main.dart5
-rw-r--r--lib/models/data/recipe_data_class.dart1
-rw-r--r--lib/views/image_view.dart28
-rw-r--r--lib/views/recipe_view.dart136
-rw-r--r--lib/views/settings_view.dart5
-rw-r--r--lib/views/shoplist_view.dart2
-rw-r--r--lib/views/vote_view.dart3
-rw-r--r--lib/widgets/recipe_card_widget.dart4
-rw-r--r--pubspec.lock14
-rw-r--r--pubspec.yaml3
16 files changed, 187 insertions, 92 deletions
diff --git a/README.md b/README.md
index e4b4407..1702a82 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,21 @@
-# kulinar_app
+# TODO
-A new Flutter project.
+## Before publish
-## Details
+- working import / export
+- make recipe titles changeable across sessions
+- compress / encode exports and internal images
+- add settings explanations / subtitles
+- change info links
-### Dropdown
+## Next update
-- löschen: confirm screen
-- einkaufsliste
-- teilen
-- publish
+- average color background for recipe images
+- add search based on ingridients, ie write noodle, get auflauf, noodle salad etc
-### Headline
+## Unsure
-- edit
-- burger
-
-### Onscreen
-
-- image
-- stars
-- favorite
+- provide recipes over server(s), show source
+- scheduling system
+- voting system
+- multiple images per recipe
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 61b6c4d..1411db2 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -7,7 +7,7 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
-include: package:flutter_lints/flutter.yaml
+# include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
diff --git a/lib/constants.dart b/lib/constants.dart
index 66d0257..4e570fd 100644
--- a/lib/constants.dart
+++ b/lib/constants.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
-const cVersion = "1.3.2";
+const cVersion = "1.4.0";
// Colors
const cIconColor = Colors.white;
@@ -33,12 +33,6 @@ const cInputHintStyle = TextStyle(
fontSize: 14.0,
);
-const cRecipeTextStyle = TextStyle(
- color: Colors.black,
- fontFamily: "Montserrat",
- fontSize: 18.0,
-);
-
const cDefaultTextStyle = TextStyle(
fontFamily: "Montserrat",
fontSize: 16.0,
@@ -78,6 +72,18 @@ const cTinyTitleStyle = TextStyle(
fontSize: 15.0,
);
+const cRecipeTitleStyle = TextStyle(
+ color: Colors.black,
+ fontFamily: "Montserrat",
+ fontSize: 18.0,
+);
+
+const cRecipeSubtitleStyle = TextStyle(
+ fontFamily: "RobotoSlab",
+ color: Colors.black54,
+ fontSize: 16.0,
+);
+
const cRecipeDescriptionStyle = TextStyle(
fontFamily: "RobotoSlab",
color: Colors.black54,
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index f566c6e..39bde48 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -45,6 +45,10 @@
"infoField2": "Version",
"infoField3": "Größe",
"infoField4": "Einträge",
+ "menu1": "Teilen",
+ "menu2": "Einkaufen",
+ "menu3": "Hochladen",
+ "menu4": "Löschen",
"legalease": "Diese App unterliegt dem Urheberrecht des Entwicklers. Es gelten die Einschränkungen, Bedingungen und Berechtigungen der „BSD 2-Clause“ Lizenz. Lizenzen dritter sind unten beigefügt.",
"noContentError": "Noch nichts zu sehen.",
"noNetworkError": "Rezepte konnten nicht geladen werden.",
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 930595d..995c91a 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -183,6 +183,22 @@
"@infoField4": {
"description": "A file info text field"
},
+ "menu1": "Share",
+ "@menu1": {
+ "description": "The text for a recipe drop down menu"
+ },
+ "menu2": "Shop",
+ "@menu2": {
+ "description": "The text for a recipe drop down menu"
+ },
+ "menu3": "Upload",
+ "@menu3": {
+ "description": "The text for a recipe drop down menu"
+ },
+ "menu4": "Delete",
+ "@menu4": {
+ "description": "The text for a recipe drop down menu"
+ },
"legalease": "This app is subject to the copyright of the developer. The limitations, conditions and permissions of the “BSD 2-Clause” license apply. Third-party licenses are enclosed below.",
"@legalease": {
"description": "Some legal stuff"
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 6b74dd7..4a7908c 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -45,6 +45,10 @@
"infoField2": "Версиа",
"infoField3": "Размер",
"infoField4": "Внесение",
+ "menu1": "Выслать",
+ "menu2": "Закупится",
+ "menu3": "Загрузить",
+ "menu4": "Удалить",
"legalease": "Это приложение является объектом авторских прав разработчика. Применяются ограничения, условия и разрешения лицензии «BSD 2 Clause». Лицензии третьих личностей прилагаются ниже.",
"noContentError": "Ешё пусто.",
"noNetworkError": "Не удалось скачать рецепты.",
diff --git a/lib/main.dart b/lib/main.dart
index b76dc5b..d22d644 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -12,8 +12,6 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-// TODO: Should also probably compress / encode exports and internal images
-// TODO: Add search based on ingridients, ie write noodle, get auflauf, noodle salad etc
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -63,8 +61,7 @@ class _AppRootState extends State<AppRoot> {
ThemeData themeData() {
return ThemeData(
- // TODO: Experiment what this does
- // brightness: Brightness.light,
+ brightness: Brightness.light,
primarySwatch: cPrimarySwatchColor,
textSelectionTheme: TextSelectionThemeData(
cursorColor: cIconColor,
diff --git a/lib/models/data/recipe_data_class.dart b/lib/models/data/recipe_data_class.dart
index ebbc5e1..fd4ef22 100644
--- a/lib/models/data/recipe_data_class.dart
+++ b/lib/models/data/recipe_data_class.dart
@@ -4,7 +4,6 @@ import 'package:kulinar_app/models/recipe_class.dart';
import 'package:kulinar_app/util/storage_handler.dart';
class RecipeData {
- // TODO: What is this?
static List<Recipe> remoteRecipeList = [];
static List<Recipe> recipeList = [];
diff --git a/lib/views/image_view.dart b/lib/views/image_view.dart
index 8721d5e..615aa26 100644
--- a/lib/views/image_view.dart
+++ b/lib/views/image_view.dart
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:kulinar_app/constants.dart';
-// TODO: IMPLEMENT: zooming and multiple images possible
class ImageView extends StatelessWidget {
const ImageView({Key? key, required this.image}) : super(key: key);
@@ -12,27 +11,28 @@ class ImageView extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: _averageImageColor(image),
- body: Center(
- child: Container(
- child: GestureDetector(
- child: Hero(
- tag: "image",
- child: Image(
- image: FileImage(File(image)),
+ return InteractiveViewer(
+ child: Scaffold(
+ backgroundColor: _averageImageColor(image),
+ body: Center(
+ child: Container(
+ child: GestureDetector(
+ child: Hero(
+ tag: "image",
+ child: Image(
+ image: FileImage(File(image)),
+ ),
),
+ onTap: () {
+ Navigator.pop(context);
+ },
),
- onTap: () {
- Navigator.pop(context);
- },
),
),
),
);
}
- // TODO: IMPLEMENT: average color background
Color _averageImageColor(String path) {
return cPrimaryColor;
}
diff --git a/lib/views/recipe_view.dart b/lib/views/recipe_view.dart
index 960ba84..a0304c3 100644
--- a/lib/views/recipe_view.dart
+++ b/lib/views/recipe_view.dart
@@ -1,9 +1,9 @@
import 'dart:io';
import 'package:flutter/material.dart';
-import 'package:http/http.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';
@@ -12,10 +12,13 @@ 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/settings_data_class.dart';
-import 'package:image_picker/image_picker.dart';
+import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:path_provider/path_provider.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:image_picker/image_picker.dart';
import 'package:share_plus/share_plus.dart';
+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);
@@ -108,7 +111,7 @@ class _RecipeViewState extends State<RecipeView> {
}
}
- void _removeRecipe() {
+ void _removeRecipe(BuildContext _) {
Navigator.pop(context);
RecipeData.recipeList.remove(widget.recipe);
RecipeData.save();
@@ -118,11 +121,12 @@ class _RecipeViewState extends State<RecipeView> {
String _content = AppLocalizations.of(context)!.removed;
String _actionLabel = AppLocalizations.of(context)!.undo;
- // FIXME: This might throw due to "!"
- widget.showToastCallback!(_content, _actionLabel, () {
- _addRecipe(passedRecipe: _removedRecipe);
- widget.redrawCallback!();
- });
+ if (widget.showToastCallback != null) {
+ widget.showToastCallback!(_content, _actionLabel, () {
+ _addRecipe(passedRecipe: _removedRecipe);
+ widget.redrawCallback!();
+ });
+ }
setState(() {});
}
@@ -142,12 +146,10 @@ class _RecipeViewState extends State<RecipeView> {
setState(() {});
}
- // FIXME: doesnt refresh, should be passed from parent
void _pickImage() async {
XFile? _pickedFile;
if (SettingsData.settings["photoSource"] == "0") {
- // TODO: Maybe pick multi image?
_pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
} else {
_pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery);
@@ -167,7 +169,6 @@ class _RecipeViewState extends State<RecipeView> {
setState(() {});
}
- // FIXME: doesnt refresh, should be passed from parent
void _removeImage() async {
_unsavedRecipe.image = null;
await RecipeData.save();
@@ -202,7 +203,7 @@ class _RecipeViewState extends State<RecipeView> {
padding: const EdgeInsets.only(right: 20.0, left: 20.0),
child: TextFormField(
controller: _controller1,
- style: cRecipeTextStyle,
+ style: cRecipeTitleStyle,
cursorColor: cPrimaryColor,
enabled: _isEditingAllowed(),
focusNode: _detailsTitleFocus,
@@ -226,7 +227,6 @@ class _RecipeViewState extends State<RecipeView> {
Widget _buildImage() {
if (_unsavedRecipe.image != null) {
- // TODO: Use https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html
return Padding(
padding: const EdgeInsets.only(top: 12.0, left: 20.0, right: 20.0),
child: GestureDetector(
@@ -257,34 +257,92 @@ class _RecipeViewState extends State<RecipeView> {
Widget _buildDescriptionInput() {
return Padding(
padding: const EdgeInsets.all(18.0),
- child: TextField(
- expands: true,
- maxLines: null,
- minLines: null,
- decoration: null,
- controller: _controller2,
- cursorColor: cPrimaryColor,
- focusNode: _detailsTextFocus,
- style: cRecipeDescriptionStyle,
- readOnly: !_isEditingAllowed(),
- enableInteractiveSelection: true,
- toolbarOptions: ToolbarOptions(
- copy: true,
- cut: true,
- paste: true,
- selectAll: true,
+ child: _isEditingAllowed() ? _buildInputDescriptuion() : _buildMarkdownDescription(),
+ );
+ }
+
+ TextField _buildInputDescriptuion() {
+ return TextField(
+ expands: true,
+ maxLines: null,
+ minLines: null,
+ decoration: null,
+ controller: _controller2,
+ cursorColor: cPrimaryColor,
+ focusNode: _detailsTextFocus,
+ style: cRecipeDescriptionStyle,
+ enableInteractiveSelection: true,
+ toolbarOptions: ToolbarOptions(
+ copy: true,
+ cut: true,
+ paste: true,
+ selectAll: true,
+ ),
+ );
+ }
+
+ MarkdownBody _buildMarkdownDescription() {
+ return MarkdownBody(
+ data: _controller2.text,
+ 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,
+ ),
),
),
);
}
- void _shareData() {
+ void _shareData(BuildContext _) {
Share.share("${_unsavedRecipe.title}\n\n${_unsavedRecipe.description}", subject: _unsavedRecipe.title);
}
- void _addToShoppingList() {}
+ void _addToShoppingList(BuildContext _) {
+ final _ingredients = _unsavedRecipe.description!.split("\n")..retainWhere((element) => element.startsWith("- "));
+
+ for (String _ingredient in _ingredients) {
+ String _item = _ingredient.substring(2).trim();
+
+ if (!ShoplistData.shoplist.contains(_item)) {
+ ShoplistData.shoplist.add(_item);
+ }
+ }
+
+ ShoplistData.save();
+
+ if (widget.showToastCallback != null) {
+ // TODO: translation
+ widget.showToastCallback!("Added to Shoplist", "Ok", () {
+ _addRecipe(passedRecipe: _removedRecipe);
+ widget.redrawCallback!();
+ });
+ }
+ }
void _uploadRecipe(BuildContext context) async {
+ return;
+
Map<String, String> _headers = {"Content-Type": "application/json; charset=UTF-8"};
String _body = _unsavedRecipe.toJsonString();
Response? res;
@@ -310,13 +368,12 @@ class _RecipeViewState extends State<RecipeView> {
List<Widget> _localActions = [
_buildAppBarCheckActions(),
PopupMenuButton(
- onSelected: (int value) => _actionList.elementAt(value)(),
+ onSelected: (int value) => _actionList.elementAt(value)(context),
itemBuilder: (BuildContext context) => [
- // TODO: Translations
- PopupMenuItem<int>(value: 0, child: Text("Teilen")),
- PopupMenuItem<int>(value: 1, child: Text("Einkaufen")),
- PopupMenuItem<int>(value: 2, child: Text("Hochladen")),
- PopupMenuItem<int>(value: 3, child: Text("Löschen")),
+ PopupMenuItem<int>(value: 0, child: Text(AppLocalizations.of(context)!.menu1)),
+ PopupMenuItem<int>(value: 1, child: Text(AppLocalizations.of(context)!.menu2)),
+ PopupMenuItem<int>(value: 2, child: Text(AppLocalizations.of(context)!.menu3)),
+ PopupMenuItem<int>(value: 3, child: Text(AppLocalizations.of(context)!.menu4)),
],
),
];
@@ -333,12 +390,13 @@ class _RecipeViewState extends State<RecipeView> {
List<Widget> _remoteLookupAction = [
IconButton(
icon: Icon(Icons.menu_open_rounded),
- onPressed: null, // TODO: IMPLEMENT: Show local recipe
+ onPressed: null,
),
];
if (widget.remote && RecipeData.recipeList.contains(recipe)) return _remoteLookupAction;
if (widget.remote) return _remoteDownloadAction;
+
return _localActions;
}
diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart
index 7f2ae90..d217bb7 100644
--- a/lib/views/settings_view.dart
+++ b/lib/views/settings_view.dart
@@ -15,7 +15,6 @@ class SettingsView extends StatefulWidget {
_SettingsViewState createState() => _SettingsViewState();
}
-// TODO: Add subtitles
class _SettingsViewState extends State<SettingsView> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
@@ -54,7 +53,7 @@ class _SettingsViewState extends State<SettingsView> {
title: Text(AppLocalizations.of(context)!.setting22, style: cDefaultTextStyle),
trailing: IconButton(
icon: Icon(Icons.arrow_forward_rounded),
- onPressed: null, // TODO: IMPLEMENT: specific server settings
+ onPressed: null,
),
),
_buildTinyTitle(AppLocalizations.of(context)!.settingTitle3),
@@ -93,7 +92,7 @@ class _SettingsViewState extends State<SettingsView> {
);
}
- void _updateSettings(String text) {
+ void _updateSettings(String? text) {
_focusNode.unfocus();
if (text != null) {
diff --git a/lib/views/shoplist_view.dart b/lib/views/shoplist_view.dart
index a9caed8..500b8fc 100644
--- a/lib/views/shoplist_view.dart
+++ b/lib/views/shoplist_view.dart
@@ -113,7 +113,7 @@ class _ShoplistView extends State<ShoplistView> {
padding: const EdgeInsets.all(15.0),
child: TextFormField(
controller: _controller,
- style: cRecipeTextStyle.copyWith(color: cIconColor),
+ style: cRecipeTitleStyle.copyWith(color: cIconColor),
cursorColor: cIconColor,
focusNode: _focusNode,
textInputAction: TextInputAction.done,
diff --git a/lib/views/vote_view.dart b/lib/views/vote_view.dart
index 7de2d10..86ddc0b 100644
--- a/lib/views/vote_view.dart
+++ b/lib/views/vote_view.dart
@@ -12,14 +12,13 @@ class VoteView extends StatefulWidget {
_VoteViewState createState() => _VoteViewState();
}
-// TODO: Change from vote to schedule
class _VoteViewState extends State<VoteView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.category6)),
drawer: CustomDrawer(initalIndex: 3),
- body: NoContentError(), // TODO: IMPLEMENT: A voting system
+ body: NoContentError(),
);
}
}
diff --git a/lib/widgets/recipe_card_widget.dart b/lib/widgets/recipe_card_widget.dart
index d96b004..22e50ef 100644
--- a/lib/widgets/recipe_card_widget.dart
+++ b/lib/widgets/recipe_card_widget.dart
@@ -140,7 +140,7 @@ class __RecipeInfoState extends State<_RecipeInfo> {
padding: const EdgeInsets.all(2.0),
child: Text(
widget.recipe.title!,
- style: cRecipeTextStyle,
+ style: cRecipeTitleStyle,
overflow: TextOverflow.ellipsis,
),
),
@@ -156,7 +156,7 @@ class __RecipeInfoState extends State<_RecipeInfo> {
Padding(
padding: const EdgeInsets.only(bottom: 2.0),
child: Row(
- children: widget.recipe.rating != null ? _buildRating(widget.recipe.rating) : [],
+ children: _buildRating(widget.recipe.rating),
),
),
],
diff --git a/pubspec.lock b/pubspec.lock
index 4b370fa..bd333ce 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -151,6 +151,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_markdown:
+ dependency: "direct main"
+ description:
+ name: flutter_markdown
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.6.10+2"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -238,6 +245,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
+ markdown:
+ dependency: transitive
+ description:
+ name: markdown
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.0.0"
matcher:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 84fb5b2..6b40405 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.3.3
+version: 1.4.0
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -34,6 +34,7 @@ dependencies:
image_picker: ^0.8.5
external_path: ^1.0.1
path_provider: ^2.0.11
+ flutter_markdown: ^0.6.10
permission_handler: ^10.0.0
shared_preferences: ^2.0.15
flutter_local_notifications: ^9.7.0