aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-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
12 files changed, 155 insertions, 73 deletions
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),
),
),
],