diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/constants.dart | 20 | ||||
-rw-r--r-- | lib/l10n/app_de.arb | 4 | ||||
-rw-r--r-- | lib/l10n/app_en.arb | 16 | ||||
-rw-r--r-- | lib/l10n/app_ru.arb | 4 | ||||
-rw-r--r-- | lib/main.dart | 5 | ||||
-rw-r--r-- | lib/models/data/recipe_data_class.dart | 1 | ||||
-rw-r--r-- | lib/views/image_view.dart | 28 | ||||
-rw-r--r-- | lib/views/recipe_view.dart | 136 | ||||
-rw-r--r-- | lib/views/settings_view.dart | 5 | ||||
-rw-r--r-- | lib/views/shoplist_view.dart | 2 | ||||
-rw-r--r-- | lib/views/vote_view.dart | 3 | ||||
-rw-r--r-- | lib/widgets/recipe_card_widget.dart | 4 |
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), ), ), ], |